微信小程序基于Canvas实现头像图片裁剪(下)

背景

在上一篇,我们已经成功把图片按 path 绘制到 Canvas 上,也搞定了裁剪区域,这可是完成了相当关键的部分。接下来,就要实现移动图片、缩放图片以及保存图片这些功能了。

移动图片

先从移动图片这个相对简单的任务开始。思路就是,当手指在 Canvas 上点击并移动,我们通过计算手指移动的位置和图片当前位置,在 Canvas 上重新绘制图片。这里要用到微信小程序的touchstarttouchmovetouchend这三个事件,它们分别对应手指触摸动作开始👆、手指触摸后移动🖌️、手指触摸动作结束👇 。

html 复制代码
<canvas
    id="canvas"
    type="2d"
    style="width: 100%; height: 100%;"
    bind:touchstart="onTouchStart"
    bind:touchmove="onTouchMove"
    bind:touchend="onTouchEnd">
</canvas>

touchstart

js 复制代码
onTouchStart(e) {
    const { touches } = e
    if (touches.length > 2) return

    let move = null, scale = null
    if (touches.length === 1) {
      // 单指触摸
      const { pageX, pageY } = touches[0]
      move = { startX: pageX, startY: pageY, }
      this.setData({ start: true, move, scale })
    } 
}

小程序的touch事件有个touches属性,它能告诉我们有几个手指在屏幕上操作。因为是移动图片,单指操作就行。在touchstart事件里,我们把手指触摸屏幕时的坐标记录下来 。

touchmove

js 复制代码
onTouchMove(e) {
    if (this.data.start) {
         const { ctx, width, height } = this.data.canvas
        ctx.clearRect(0,0, width, height)
        const { touches } = e
        if (touches.length === 1) {
            const { startX, startY } = this.data.move
            const { pageX, pageY } = touches[0]
            const offsetX = pageX - startX
            const offsetY = pageY - startY

            const { target: image, width, height, x: imageX, y: imageY } = this.data.image
            const { ctx } = this.data.canvas
            const x = imageX + offsetX
            const y = imageY + offsetY
            ctx.drawImage(image, x, y, width, height);
            this.setData({
              changeData: { x, y, width, height }
            })
        }
    }
}

当手指移动时,我们要重新绘制图片的位置。移动过程中图片大小不变,重点是计算坐标位置 。移动时先清空画布,接着获取当前触摸点的坐标pageXpageY,用它们减去触摸起始坐标,得到移动后的坐标偏移量,再将图片原坐标加上偏移量,得到新位置。然后调用ctx.drawImage重新绘制图片,并保存移动后的图片位置数据。注意,移动时先不绘制裁剪区域,不然画面会闪,影响体验 。

touchend

kotlin 复制代码
  onTouchEnd(e) {
    const { changeData } = this.data
    if (changeData) {
      const { x, y, width, height } = changeData
      this.setData({
        'image.width': width,
        'image.height': height,
        'image.x': x,
        'image.y': y,
        changeData: null
      })
      this.drawCrop()
    }
    this.setData({ start: false })
  },

最后,当手指松开,我们把移动过程中的数据保存好,同时将裁剪区域重新绘制到画布上 。

缩放图片

图片缩放功能和移动有些相似,但计算会更复杂一点 。

touchstart

js 复制代码
// 计算直径
caleDiameter(touches) {
    const [first, second] = touches
    const x = first.pageX - second.pageX
    const y = first.pageY - second.pageY
    return Math.abs(Math.sqrt( x * x + y * y ))
},

onTouchStart(e) {
    const { touches } = e
    if (touches.length > 2) return
    if (touches.length === 2) {
        let scale = null
        if (touches.length === 2) {
          // 双指触摸
          const startDiameter = this.caleDiameter(touches)
          const [first, second] = touches
          scale = { 
            firstStartX: first.pageX,
            firstStartY: first.pageY,
            secondStartX: second.pageX,
            secondStartY: second.pageY,
            startDiameter
          }
          this.setData({ scale })
        } 
    }
}

onTouchStart 函数用来处理触摸开始事件。它从传入的事件对象 e 里取出 touches 数组,这里面存着触摸点信息。 先检查 touches 长度,要是超过 2 个触摸点,就啥也不干直接返回,因为咱只处理双指操作。 当触摸点是 2 个(也就是双指触摸)时,声明 scale 变量,然后调用 caleDiameter 方法算出双指初始间距存到 startDiameter 里,再把两个触摸点分别解构到 firstsecond 变量。接着构建 scale 对象,把双指的初始坐标和间距都放进去,最后通过 setDatascale 存到组件数据里,为后续缩放操作提供初始数据 。

touchmove

onTouchMove 函数用于处理触摸移动事件,当手指在屏幕上滑动时,它就会被触发,其核心任务是实现图片的缩放功能。

js 复制代码
onTouchMove(e) {
     const { touches } = e
    // 计算缩放比例
    const { startDiameter } = this.data.scale
    const diameter = this.caleDiameter(touches)
    const scale = diameter / startDiameter
    // 计算缩放后的尺寸
    const { target: image, x: imageX, y: imageY, width: imageWidth, height: imageHeight } = this.data.image
    const { ctx } = this.data.canvas
    const width = imageWidth * scale
    const height = imageHeight * scale
    // 计算缩放后的偏移位置
    const { 
      firstStartX, firstStartY,
      secondStartX, secondStartY,
    } = this.data.scale
    // 中心坐标
    const centerX = (firstStartX + secondStartX) / 2
    const centerY = (firstStartY + secondStartY) / 2
    // 相对于图片坐标的偏移量
    const offsetX = centerX - imageX
    const offsetY = centerY - imageY
    // 新的偏移量
    const newOffsetX = offsetX * scale
    const newOffsetY = offsetY * scale
    // 图片坐标
    const x = centerX - newOffsetX
    const y = centerY - newOffsetY
    ctx.drawImage(image, x, y, width, height);
    this.setData({
      changeData: { x, y, width, height }
    })
}
主要步骤
  1. 获取触摸点信息 :从传入的事件对象 e 中提取 touches 数组,这个数组包含了当前所有触摸点的详细信息,我们后续的计算都要基于这些信息展开。
  2. 计算缩放比例 :从 this.data.scale 中取出双指触摸开始时的间距 startDiameter,再调用 caleDiameter 方法计算当前双指的间距 diameter。将这两个间距相除,就得到了缩放比例 scale。这个比例决定了图片是放大还是缩小。
  3. 计算缩放后尺寸 :从 this.data.image 中获取图片的原始信息,像图片对象、原始坐标以及宽高。把原始的宽度和高度分别乘以缩放比例,就得到了缩放后图片的宽度和高度。
  4. 计算缩放后偏移位置
    • this.data.scale 中取出双指触摸开始时的坐标。
    • 算出双指的中心坐标,这个中心坐标可以帮助我们确定图片缩放的基准点。
    • 计算中心坐标相对于图片原始坐标的偏移量,再将这个偏移量乘以缩放比例,得到新的偏移量。
    • 用中心坐标减去新的偏移量,就得到了缩放后图片的坐标。
  5. 重新绘制图片 :调用 ctx.drawImage 方法,根据新的坐标和尺寸,在 Canvas 上重新绘制图片,让我们看到缩放后的效果。
  6. 保存缩放后图片信息 :使用 this.setData 把缩放后的图片坐标和尺寸存到 changeData 里,方便后续处理,比如在手指松开时进一步保存这些数据。

通过上述一系列操作,这段代码能够根据双指移动时间距的变化,动态地计算缩放比例以及新的图片位置和尺寸,然后重新绘制图片,实现图片的缩放效果。

touchend

touchend事件的处理和移动图片时类似,这里就不再重复贴代码啦 。

保存图片

最后我们只需要调用wx.canvasToTempFilePathAPI获取到临时图片路径,然后进行上传即可。

🦀🦀感谢看官看到这里,如果觉得文章不错的话🙌,点个关注不迷路⭐。

诚邀您加入我的微信技术交流群🎉,群里都是志同道合的开发者👨‍💻,大家能一起交流分享摸鱼🐟。期待与您在群里相见🚀,咱们携手在开发路上共同进步✨ ! 👉点我
感谢各位大侠一路相伴,实在感激! 不瞒您说,在下还有几个开源项目 📦,它们就像精心培育的幼苗 🌱,急需您的浇灌。要是您瞧着还不错,麻烦动动手指,给它们点亮几颗 Star ⭐,您的支持就是它们成长的最大动力,在此谢过各位大侠啦!

相关推荐
Allen Bright31 分钟前
【XML基础-2】深入理解XML中的语义约束:DTD详解
xml·前端
Attacking-Coder2 小时前
前端面试宝典---闭包
前端
开心小老虎3 小时前
threeJs实现裸眼3D小狗
前端·3d·threejs
龙井>_<4 小时前
vue3使用keep-alive缓存组件与踩坑日记
前端·vue.js·缓存
彭铖洋5 小时前
VSCode会击败Cursor和Windsurf吗?
javascript·reactjs
Captaincc5 小时前
OpenAI以API的形式发布了三 个新模型:GPT-4.1、GPT-4.1 mini 和 GPT-4.1 nano
前端·openai
HelloRevit6 小时前
Next.js 快速启动模板
开发语言·javascript·ecmascript
A-Kamen7 小时前
Webpack vs Vite:深度对比与实战示例,如何选择最佳构建工具?
前端·webpack·node.js
codingandsleeping7 小时前
OPTIONS 预检请求
前端·网络协议·浏览器
程序饲养员8 小时前
ReactRouter7.5: NavLink 和 Link 的区别是什么?
前端·javascript·react.js