微信小程序图片裁剪功能的实现

article/2025/11/8 9:13:48

文章目录

    • 图片上传与处理
    • 图片尺寸适配
    • 图片显示与裁剪框
    • 裁剪框的拖动与缩放
    • 增加canvas并裁剪图片
    • 保存图片到相册
    • 总结

在之前的博文中,已经介绍了如何使用在前端开发中,实现较方便自由的图片裁剪功能,可见博文: 如何一步步实现图片裁剪功能。
本文将进一步讲述在微信小程序中,我们实现图片裁剪功能需要如何处理,文章末尾将附上小程序端图片裁剪完整源码的下载链接。
在小程序中进行图片裁剪的效果如下图所示:

在这里插入图片描述

图片上传与处理

要做图片裁剪,首先要解决的是图片上传的事情,这块在微信环境下,比较好处理,微信小程序提供了多个文件或图片的操作接口,让我们可以很方便的完成这个步骤。

本博之前关于小程序的图片审核的文章:小程序图片上传与内容安全审核,已介绍了使用 wx.chooseMedia 接口来选择图片文件。
除此外,还可以有其他方式,如 wx.chooseMessageFile 接口可以从聊天记录里选择图片,我们可以综合处理如下:

function selectPhoto (callBack) {let itemList = [ '拍照', '从手机相册选择', '从聊天记录选择' ]wx.showActionSheet({itemList,success: (action) => {if (action.tapIndex === 0) {// 从拍照选择图片,使用 chooseMediawx.chooseMedia({ sourceType: [ 'camera' ] })} else if (action.tapIndex === 1) {// 从手机相册选择图片,使用 chooseMediawx.chooseMedia({ sourceType: [ 'album' ] })} else if (action.tapIndex === 2) {// 从聊天记录选择图片,使用 chooseMessageFilewx.chooseMessageFile({})}}})
}

其中,showActionSheet 可以显示操作菜单,这个功能也比较常见,可以从拍照、相册、聊天记录里选择文件进行加载:

在这里插入图片描述
当前,我们获取到的是图片文件的临时路径,得到图片路径以后,接下来要做的事情,就是如何正确适配的显示出来。

图片尺寸适配

由于要在前端对图片进行裁剪,使用canvas绘制图片也是不可少的,在绘制之前,我们需要根据图片尺寸进行适配处理。
首先需要做的就是读取图片的尺寸大小,使用小程序的 wx.getImageInfo 接口即可,它能够返回图片原始的宽高属性。
接着,根据图片的宽高属性、系统屏幕的宽高,一起在小程序页面可见区域内设置图片的缩放显示:

// 根据窗口大小和图片大小,设置图片显示尺寸以及缩放
// imgHeight 和 imgWidth 表示当前图片的宽高
// 设置图片显示面板的宽高
let panelW = sys.windowWidth - 20
let panelH = sys.windowHeight - 100
if (panelH / panelW >= imgHeight / imgWidth) {panelH = parseInt(panelW * imgHeight / imgWidth)
} else {panelW = parseInt(panelH * imgWidth / imgHeight)
}
// 图片在宽高上的缩放比,用于裁剪图片时计算图片实际尺寸
xScale = panelW / imgWidth
yScale = panelH / imgHeight
this.setData({imagePath,// 图片显示面板宽高panelWidth: panelW,panelHeight: panelH,// 裁剪框的宽高,初始时与图片面板一样clipWidth: panelW,clipHeight: panelH,// 裁剪的实际宽高croppingImageWidth: imgWidth,croppingImageHeight: imgHeight
})

通过以上代码和注释,我们知道了图片加载时,需要计算的几个宽高尺寸值,都是比较重要的。

图片显示与裁剪框

下面就可以在页面上显示图片,直接展示图片显示区域的 wxml 代码:

<view wx:if="{{imagePath}}" class="crop-container"><view class="crop-content" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}};"><image src="{{imagePath}}" class="pos-absolute" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}}; left: 0; top:0;"/><view class="mask-bg" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}}; left: 0; top: 0;"></view><view class="clip-path" style="width: {{clipWidth + 'px'}}; height: {{clipHeight + 'px'}}; left: {{clipX + 'px'}}; top: {{clipY + 'px'}};"><image src="{{imagePath}}" class="pos-absolute" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}}; left: {{clipImgX + 'px'}}; top: {{clipImgY + 'px'}};"/></view><view class="clip-box"  bind:touchmove="touchmoveM" bind:touchstart="touchstartM" style="width: {{clipWidth + 'px'}}; height: {{clipHeight + 'px'}}; left: {{clipX + 'px'}}; top: {{clipY + 'px'}};"><view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="leftTop" class="corner-area left-top"></view><view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="rightTop" class="corner-area right-top"></view><view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="rightBottom" class="corner-area right-bottom"></view><view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="leftBottom" class="corner-area left-bottom"></view></view></view>
</view>

以上代码中,

  • .crop-content
    外层增加图片面板,图片资源直接使用 <image> 组件加载,与外层面板宽高保持一致;
  • .mask-bg
    增加一个遮罩层,作为非裁剪区域的蒙层,以黑灰色透明度实现效果;
  • .clip-path
    于图片面板内部,设置图片的裁剪区域,这里使用的是CSS中的 clip-path 属性,内部加载一张图片,作为裁剪区域的图片显示;
    clip-path 属性之前已有博文介绍,可以查看 clip-path属性
  • clip-box
    设置裁剪框区域的四个corner角:左上、右上、右下和左下,里面四个元素对应这个四个角,可以对裁剪框进行缩放处理;
    当然,我们也可以在各边的中间位置再加上操作区,那一共就是8个;
  • .clip-path 图片裁剪区与 clip-box 裁剪框需要设置位置信息,用于在移动的时候进行定位。

通过设置对应的图片元素、蒙层、裁剪框等等,我们就已经完成了图片裁剪的结构布局了,具体可见下图所示:

在这里插入图片描述
图片裁剪框设置完成后,我们需要处理的就是裁剪框的拖动与缩放事件处理了。

裁剪框的拖动与缩放

上面的 wxml 代码中,在裁剪框的元素部分,已经增加了 touchstarttouchmove 等事件,用于在处理拖动和缩放等操作。
当我们实现裁剪框的拖动,只需要如下两个事件:

touchstartM (event) {const { clipX, clipY } = this.dataconst { pageX, pageY } = event.touches[0]// 获取鼠标点在裁剪框的内部位置信息clipBoxMoveInnerX = pageX - clipXclipBoxMoveInnerY = pageY - clipY
},
touchmoveM (event) {const { pageX, pageY } = event.touches[0]const { panelWidth, panelHeight, clipHeight, clipWidth } = this.data// 裁剪框不能脱离面板// X位置范围为 0 到 (面板宽度-裁剪框宽度)let clipX = pageX - clipBoxMoveInnerXclipX = Math.max(clipX, 0)const panelX = panelWidth - clipWidthclipX = Math.min(clipX, panelX)// Y位置范围为 0 到 (面板高度-裁剪框高度)let clipY = pageY - clipBoxMoveInnerYclipY = Math.max(clipY, 0)const panleY = panelHeight - clipHeightclipY = Math.min(clipY, panleY)// 裁剪框底图位置信息const clipImgX = 0 - clipXconst clipImgY = 0 - clipYthis.setData({clipX,clipY,clipImgX,clipImgY})
}

以上代码,通过计算图片移动时的相对位移,重新计算裁剪框的新的位置信息,需要注意的是,移动时不要脱离外层的面板——即不能脱离图片区域,否则无效。

缩放的操作则相对复杂一些,需要计算位移移动的距离以及当前位置下的裁剪宽高数据,并且要对每个不同的corner角进行特殊处理,这块的完整代码和注释如下所示:

// 处理缩放动作中不同corner时的尺寸位置信息
getClipX (clipWidth) {switch (activeCorner) {case 'leftTop':case 'leftBottom':return clipBoxBeforeScaleClipX + (clipBoxBeforeScaleWidth - clipWidth)case 'rightTop':case 'rightBottom':return clipBoxBeforeScaleClipXdefault:return 0}
},
getClipY (clipHeight) {switch (activeCorner) {case 'leftTop':case 'rightTop':return clipBoxBeforeScaleClipY + (clipBoxBeforeScaleHeight - clipHeight)case 'leftBottom':case 'rightBottom':return clipBoxBeforeScaleClipYdefault:return 0}
},
getScaleXWidthOffset (offsetW) {switch (activeCorner) {case 'leftTop':case 'leftBottom':return -offsetWcase 'rightTop':case 'rightBottom':return offsetWdefault:return 0}
},
getScaleYHeightOffset (offsetH) {switch (activeCorner) {case 'rightBottom':case 'leftBottom':return offsetHcase 'rightTop':case 'leftTop':return -offsetHdefault:return 0}
},
touchstart (event) {const dragId = event.currentTarget.dataset.idconst { pageX, pageY } = event.touches[0]const { clipX, clipY, clipHeight, clipWidth } = this.data// 设置缩放时临时变量初始化值activeCorner = dragIdclipBoxBeforeScalePageX = pageXclipBoxBeforeScalePageY = pageYclipBoxBeforeScaleClipX = clipXclipBoxBeforeScaleClipY = clipYclipBoxBeforeScaleWidth = clipWidthclipBoxBeforeScaleHeight = clipHeight
},
touchmove (event) {const { pageX, pageY } = event.touches[0]const { panelWidth, panelHeight } = this.data// 缩放在X上的偏移const xWidthOffset = this.getScaleXWidthOffset(pageX - clipBoxBeforeScalePageX)// 裁剪框最小宽度36let clipWidth = Math.max(clipBoxBeforeScaleWidth + xWidthOffset, 36)// 设置缩放最大宽度,放大时不能超过面板、缩小时不能超过初始裁剪框let tempPanelWidth = pageX > clipBoxBeforeScalePageX ? panelWidth - clipBoxBeforeScaleClipX : clipBoxBeforeScaleWidth + clipBoxBeforeScaleClipX// 设置裁剪框宽度clipWidth = Math.min(clipWidth, tempPanelWidth)// 缩放在Y上的偏移const yHeightOffset = this.getScaleYHeightOffset(pageY - clipBoxBeforeScalePageY)// 裁剪框最小高度36let clipHeight = Math.max(clipBoxBeforeScaleHeight + yHeightOffset, 36)// 设置缩放最大高度,放大时不能超过面板、缩小时不能超过初始裁剪框let tempPanelHeight = pageY > clipBoxBeforeScalePageY ? panelHeight - clipBoxBeforeScaleClipY : clipBoxBeforeScaleHeight + clipBoxBeforeScaleClipY// 设置裁剪框高度clipHeight = Math.min(clipHeight, tempPanelHeight)// 裁剪框位置信息let clipX = this.getClipX(clipWidth)let clipY = this.getClipY(clipHeight)// 裁剪框底图位置信息let clipImgX = 0 - clipXlet clipImgY = 0 - clipYthis.setData({clipWidth,clipHeight,clipX,clipY,clipImgX,clipImgY,croppingImageWidth: parseInt(clipWidth / xScale),croppingImageHeight: parseInt(clipHeight / yScale)})
}

至此,图片裁剪的功能基本完成了,能够加载图片、设置裁剪框、拖动和缩放裁剪框大小,计算裁剪图片的尺寸等等。
就剩下如何真正剪裁图片了。

增加canvas并裁剪图片

要剪裁图片,我们在小程序使用canvas画布组件来处理,在页面上增加一个canvas元素:

<canvas id="main" canvasId="main" class="main-canvas" style="width: {{croppingImageWidth + 'px'}}; height: {{croppingImageHeight + 'px'}}"></canvas>

由于这个canvas只是用来对图片进行裁剪操作,并不需要显示出来,我们可以把它定位到视觉窗口以外,不影响可操作的界面元素。
给画布设置图片裁剪所需要的宽高,通过在同等宽高下绘制图片,就能导出图片:

wx.showLoading({ title: '正在裁剪...' })
preCtx.clearRect(0, 0, imageWidth, imageHeight)
const width = croppingImageWidth
const height = croppingImageHeight
const xPos = Math.abs(clipImgX / xScale)
const yPos = Math.abs(clipImgY / yScale)
// 绘制裁剪区内的图片到画布上
preCtx.drawImage(imagePath, xPos, yPos, width, height, 0, 0, width, height)
preCtx.save()
preCtx.restore()
const that = this
preCtx.draw(false, function () {setTimeout(() => {// 将画布导出为临时图片文件wx.canvasToTempFilePath({x: 0,y: 0,width,height,destWidth: width,destHeight: height,canvasId: 'main',success: (canRes) => {wx.hideLoading()that.initImage(width, height, canRes.tempFilePath)},fail (err) {wx.hideLoading()console.log(err)}})}, 200)
})

如上代码所示,通过裁剪图片的真实宽高以及相对位置信息,通过canvas的 drawImage 方法,把图片的裁剪区域的内容绘制到画布上,此时,该画布就对应一张裁剪后的图片,我们只需要把画布导出成图片文件即可。
导出画布,使用 wx.canvasToTempFilePath 方法,用于将画布导出为图片临时图片文件,这个图片文件可以重新加载或者进行导出。

保存图片到相册

以上过程,生成裁剪图片的临时文件后,就可以使用小程序提供的API,将图片文件保存到相册中。
只需要使用 wx.saveImageToPhotosAlbum 方法,专门用于将图片文件保存到系统相册,接收临时文件作为参数:

wx.saveImageToPhotosAlbum({filePath: imgSrc,success: () => {wx.hideLoading()wx.vibrateShort()wx.showModal({content: '图片已保存到相册~',showCancel: false,confirmText: '好的',confirmColor: '#333'})}
})

该方法简单方便,其中使用 wx.vibrateShort() 方法,作用是使手机发生较短时间的振动(15 ms),在小程序中也是常见的功能。
图片保存到系统相册功能完成后,我们就实现了在小程序中进行图片剪裁的完整功能,包含加载图片、图片适配和裁剪框绘制、裁剪框拖动与缩放事件、图片导出和保存的过程。

总结

本文详细讲述了在小程序中实现一个图片裁剪功能的过程,可以看出和在浏览器Web端的实现差别并不大。
在核心的图片适配、裁剪框绘制与缩放事件处理上,基本两边可以通用,在小程序中的实现逻辑可以完整在移到web浏览器上,反之亦然。
区别可能只在于图片的加载和保存上,可以使用小程序提供的多种内置接口方法,能较方便的完成。
上文也附上了大量的核心代码,根据这些代码已经基本可以还原裁剪功能,如果需要完整的小程序图片裁剪代码,可以访问下载:小程序图片裁剪源码下载


http://chatgpt.dhexx.cn/article/tAoTAoXc.shtml

相关文章

微信小程序第四篇:生成图片并保存到手机相册

系列文章传送门&#xff1a; 微信小程序第一篇&#xff1a;自定义组件详解 微信小程序第二篇&#xff1a;七种主流通信方法详解 微信小程序第三篇&#xff1a;获取页面节点信息 目录 一、封装分享组件 二、定义用户授权方法 三、调用流程 首先我们看一下要完成的效果&#x…

微信小程序 怎么插入图片?image组件的使用教程。

这期我们来学学怎么在小程序中插入图片 1.image组件的简介 是小程序中一个图片的组件 image组件有一个默认宽度和高度&#xff08;宽300px&#xff0c;高240px&#xff09; 支持 JPG、PNG、SVG、WEBP、GIF 等格式&#xff0c;2.3.0 起支持云文件ID。 我们先添加一个image组件给…

微信小程序-image(图片)

在微信小程序中&#xff0c;要显示一张图片&#xff0c;有两种图片加载方式&#xff1a; 加载本地图片加载网络图片 加载本地图片 <image class"widget__arrow" src"/image/arrowright.png" mode"aspectFill"> </image> src"/…

MTCNN人脸检测算法

人脸检测是指识别数字图像中的人脸。人脸检测可以视为目标检测的一种特殊情况。在目标检测中&#xff0c;任务是查找图像中特定类的所有对象的位置和大小。例如行人和汽车。 在人脸检测中应用较广的算法就是MTCNN&#xff08; Multi-task Cascaded Convolutional Networks的缩…

于仕琪的人脸检测算法

转自&#xff1a;https://blog.csdn.net/Jacky_Ponder/article/details/51819273 于仕琪的人脸检测算法&#xff0c;对Windows下的商业使用也免费。刚更新了一次算法&#xff1a;①正面人脸检测的角度范围从[-40, 40]度提升到[-60,60]度&#xff0c;检测角度变大但计算量不增加…

10种轻量级人脸检测算法大PK

几个月前&#xff0c;AIZOO曾经盘点过 最强六大开源轻量级人脸检测项目分析 | 附打包下载&#xff0c;nihate同学将它丰富到10种算法&#xff0c;并用Python.对他们进行了汇总整理&#xff0c;以及效果的对比。 Github链接&#xff1a;https://github.com/hpc203/10kinds-ligh…

目前最好的人脸检测算法,RetinaFace论文详解

原文链接&#xff1a;https://www.jianshu.com/p/d4534ac94a65 微信搜索&#xff1a;AI算法与图像处理&#xff0c;最新干货全都有 大家好&#xff0c;今天给大家分享一篇人脸算法领域非常知名的paper&#xff0c;RetinaFace(RetinaFace: Single-stage Dense Face Localisation…

人脸检测算法理解之mtcnn

最近在一次交流活动中&#xff0c;再次听别人讲人脸检测算法mtcnn&#xff0c;虽然以前也断断续续听过两次&#xff0c;对于一些技术细节仍不清楚&#xff0c;为了解决自己的困惑&#xff0c;笔者又重拾起这一算法&#xff0c;在认真研读论文和思考后有了新的理解&#xff0c;于…

人脸检测算法总结:PyramidBox

人脸检测算法总结&#xff1a;PyramidBox IntroductionPyramidBoxTraining PyramidBox是百度提出的人脸检测算法&#xff0c;提出后在widerface上排第一&#xff08;现在已经不是了&#xff0c;但仍居前三&#xff09;&#xff0c;PyramidBox可以看做是S3FD的升级版&#xff0c…

人脸检测算法总结:S3FD

人脸检测算法总结&#xff1a;S3FD——Single Shot Scale-invariant Face Detector IntroductionS3FDTraining结论 S3FD是中科院自动化所的一篇文章&#xff0c;当时一出现就刷新了wider face榜单&#xff0c;比上一篇提到的SSH略高一点。这个算法主要是解决小人脸的检测问题&a…

SSH人脸检测算法(SSH: Single Stage Headless Face Detector)

论文&#xff1a;《SSH: Single Stage Headless Face Detector》 链接&#xff1a;https://arxiv.org/pdf/1708.03979.pdf 代码链接&#xff1a;https://github.com/mahyarnajibi/SSH 摘要 我们介绍了单点无头&#xff08;SSH&#xff09;人脸检测器。与两阶段提议分类检测器…

人脸检测算法MTCNN

本文将对人脸检测效果不错的MTCNN算法做一个梳理。 论文地址 MTCNN主要分为三步&#xff1a; 最初先对图像进行多尺度变换&#xff0c;获取图像金字塔&#xff0c;获取图像多尺度信息。 P-net(Proposal Network)&#xff1a; 主要使用一个全卷机网络&#xff0c;获取候选框…

YbFace:完全开源的人脸检测算法

YbFase(yolo base face detector),是一个借鉴了yolov3的网络结构,在多特征尺度下预测人脸框的人脸检测算法.并结合了yolov4使用的mish激活函数.该模型大小仅为7,8M,无论模型大小,还是准确率,还是执行速率都很有竞争力. 源码地址(github)&#xff1a; face_detect 如果对你有…

适合ARM 的轻量级人脸检测算法汇总

目录 1、场景需求2、libfacedetection2.1 相关链接2.2 算法简介2.3 算法效果展示2.4 算法性能展示 3、Ultra-Light-Fast-Generic-Face-Detector-1MB3.1 相关链接3.2 算法简介3.3 算法效果展示3.4 算法性能展示 4、BlazeFace4.1 相关链接4.2 算法简介4.3 算法效果展示4.4 算法性…

opencv算法+人脸检测

文章目录 ORB算法视频读写图像人脸识别摄像头实时人脸检测 ORB算法 orb算法结合了Fast和Brief算法&#xff0c;提出了构造金字塔&#xff0c;为Fast特征点添加了方向&#xff0c;从而使得关键点具有了尺度不变性和旋转不变性。 # orb算法结合了Fast和Brief算法&#xff0c;提…

人脸检测算法对比分析

&#xfeff;&#xfeff; https://blog.csdn.net/twinkle_star1314/article/details/53318689 人脸识别包括以下5个步骤&#xff1a;人脸检测、图像预处理、特征提取、匹配、结果输出。 人脸检测是人脸识别中的第一个环节&#xff0c;是一项关键技术。人脸检测是指假设在输入…

人脸检测算法:mtcnn简介

人脸检测算法:MTCNN简介 算法流程网络介绍训练方法 MTCNN是一个针对于人脸检测的模型, 由PNet, RNet, 和ONet三个网络组成,三个网络都比较轻量, 易于训练.基于pytorch的实现可以看我这篇&#xff1a; 链接: l利用celebA数据集训练MTCNN网络. 算法流程 1. 对传入P网络的图片re…

10种轻量级人脸检测算法的比拼

关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;ComputerVisionGzq 学习群&#xff5c;扫码在主页获取加入方式 转自&#xff1a;https://blog.csdn.net/nihate/article/details/108798831 计算机视觉研究院专栏 作者&#xff1a;Edison_G 最近在微信公众号里看到…

mtcnn人脸检测算法介绍

0. 概述 MTCNN(Multi-task Cascaded Convolutional Networks&#xff0c;多任务卷积神经网络)&#xff0c;是2016年由Kaipeng Zhang、Zhanpeng Zhang等提出的一种多任务人脸检测模型&#xff0c;该模型使用3个CNN级联算法结构&#xff0c;将人脸检测和人脸特征点提取同时完成。…

Matlab人脸检测算法详解

这是一个Matlab人脸检测算法详解 前言人脸检测结果算法详解源代码解析所调用函数解析bwlabel(BW,n)regionpropsrectangle 总结 前言 目前主流的人脸检测与人脸识别算法主要基于人工神经网络进行训练与检测&#xff0c;本文基于数字图像处理解析一个非人工神经网络的有趣人脸检…