小程序图片裁剪功能实现
发布于 1 年前 作者 chaocui 4682 次浏览 来自 分享

裁剪框的拖动与缩放

在裁剪框的元素部分,增加 touchstart 和 touchmove 等事件,用于在处理拖动和缩放等操作。

当我们实现裁剪框的拖动,只需要如下两个事件:

touchstartM (event) {
  const { clipX, clipY } = this.data
  const { pageX, pageY } = event.touches[0]
  // 获取鼠标点在裁剪框的内部位置信息
  clipBoxMoveInnerX = pageX - clipX
  clipBoxMoveInnerY = pageY - clipY
},
touchmoveM (event) {
  const { pageX, pageY } = event.touches[0]
  const { panelWidth, panelHeight, clipHeight, clipWidth } = this.data

  // 裁剪框不能脱离面板
  // X位置范围为 0 到 (面板宽度-裁剪框宽度)
  let clipX = pageX - clipBoxMoveInnerX
  clipX = Math.max(clipX, 0)
  const panelX = panelWidth - clipWidth
  clipX = Math.min(clipX, panelX)
  // Y位置范围为 0 到 (面板高度-裁剪框高度)
  let clipY = pageY - clipBoxMoveInnerY
  clipY = Math.max(clipY, 0)
  const panleY = panelHeight - clipHeight
  clipY = Math.min(clipY, panleY)

  // 裁剪框底图位置信息
  const clipImgX = 0 - clipX
  const clipImgY = 0 - clipY

  this.setData({
    clipX,
    clipY,
    clipImgX,
    clipImgY
  })
}

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

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

// 处理缩放动作中不同corner时的尺寸位置信息

getClipX (clipWidth) {
  switch (activeCorner) {
    case 'leftTop':
    case 'leftBottom':
      return clipBoxBeforeScaleClipX + (clipBoxBeforeScaleWidth - clipWidth)
    case 'rightTop':
    case 'rightBottom':
      return clipBoxBeforeScaleClipX
    default:
      return 0
  }
},
getClipY (clipHeight) {
  switch (activeCorner) {
    case 'leftTop':
    case 'rightTop':
      return clipBoxBeforeScaleClipY + (clipBoxBeforeScaleHeight - clipHeight)
    case 'leftBottom':
    case 'rightBottom':
      return clipBoxBeforeScaleClipY
    default:
      return 0
  }
},
getScaleXWidthOffset (offsetW) {
  switch (activeCorner) {
    case 'leftTop':
    case 'leftBottom':
      return -offsetW
    case 'rightTop':
    case 'rightBottom':
      return offsetW
    default:
      return 0
  }
},
getScaleYHeightOffset (offsetH) {
  switch (activeCorner) {
    case 'rightBottom':
    case 'leftBottom':
      return offsetH
    case 'rightTop':
    case 'leftTop':
      return -offsetH
    default:
      return 0
  }
},
touchstart (event) {
  const dragId = event.currentTarget.dataset.id
  const { pageX, pageY } = event.touches[0]
  const { clipX, clipY, clipHeight, clipWidth } = this.data

  // 设置缩放时临时变量初始化值
  activeCorner = dragId
  clipBoxBeforeScalePageX = pageX
  clipBoxBeforeScalePageY = pageY
  clipBoxBeforeScaleClipX = clipX
  clipBoxBeforeScaleClipY = clipY
  clipBoxBeforeScaleWidth = clipWidth
  clipBoxBeforeScaleHeight = clipHeight
},
touchmove (event) {
  const { pageX, pageY } = event.touches[0]
  const { panelWidth, panelHeight } = this.data

  // 缩放在X上的偏移
  const xWidthOffset = this.getScaleXWidthOffset(pageX - clipBoxBeforeScalePageX)
  // 裁剪框最小宽度36
  let 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)
  // 裁剪框最小高度36
  let 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 - clipX
  let clipImgY = 0 - clipY

  this.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>

1

由于这个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浏览器上,反之亦然。

区别可能只只在于图片的加载和保存上,可以使用小程序提供的多种内置接口方法,能教方便的完成。

回到顶部