微信小程序基于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 ⭐,您的支持就是它们成长的最大动力,在此谢过各位大侠啦!

相关推荐
passerby606138 分钟前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte2 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc