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

news2024/11/25 5:03:04

文章目录

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

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

在这里插入图片描述

图片上传与处理

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

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

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

由于这个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://www.coloradmin.cn/o/526208.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

直达CSDN——创作者创收

catalogue &#x1f31f; 写在前面&#x1f31f; 关于创收线下聚会&#x1f31f; 创作是最简单的变现模式&#x1f31f; 知识星球是否适合你&#x1f31f; 独自开-让开发变得简单&#x1f31f; 税/睡后收入&#x1f31f; KOL越具价值&#x1f31f; 期待创收组织的建立&#x1f…

MySQL数据库和Redis缓存一致性的更新策略

目录 专栏导读一、更新策略二、读写缓存1、同步直写策略2、异步缓写策略三、双检加锁策略四、数据库和缓存一致性的更新策略1、先更新数据库,再更新Redis2、先更新缓存,再更新数据库3、先删除缓存,再更新数据库延时双删4、先更新数据库,再删除缓存5、总结五、MySQL主从复制…

解密《永恒之塔私服》龙界要塞全貌

《永恒之塔2.0&#xff1a;进军龙界》将于12月29日14:00正式开放测试&#xff0c;全区全服29日起陆续更新&#xff0c;并将在元旦假期期间完成所有区服更新工作。12月27日&#xff0c;2.0新区抢先免费体验已经开始&#xff0c;凭特权激活码抢先免费体验无外挂《永恒之塔2.0》。…

Transformer通俗笔记:从Word2Vec、Seq2Seq逐步理解到GPT、BERT

前言 我在写上一篇博客《22下半年》时&#xff0c;有读者在文章下面评论道&#xff1a;“july大神&#xff0c;请问BERT的通俗理解还做吗&#xff1f;”&#xff0c;我当时给他发了张俊林老师的BERT文章&#xff0c;所以没太在意。 直到今天早上&#xff0c;刷到CSDN上一篇讲B…

TOGAF®10标准读书会首场活动圆满举办,精彩时刻回顾!

7月21日19:30pm&#xff0c;TOGAF10标准读书会线上系列活动-第一季首场活动圆满举办&#xff0c;读书会活动集聚了行业专家的力量&#xff0c;共同阅读、分享并结合自身实践&#xff0c;来讨论TOGAF标准第10版所阐释的内容。 首期读书会聚焦TOGAF标准第10版中的基本内容展开分享…

AI平台、AI中台架构

选择什么方式接入AI开放平台 1)公有云 形式:在开放平台注册账户,申请权限,调用对应AI能力的API服务 缺点:数据出公司域,存在数据安全 优点:价格相对低 2)私有化 形式:将AI能力以产品包的形式部署到客户的服务器上 缺点:需要后续运维支持、价格昂贵 优点:数据…

字节跳动|后端|提前批|一面+二面+三面+HR 面

一面&#xff5c;1h 自我介绍select、poll、epoll&#xff1f;epoll的两种触发模式&#xff1f;TCP三次握手过程&#xff0c;有什么状态&#xff0c;状态机如何变化&#xff1f;TCP握手的目的有哪些&#xff1f;什么是 TIME_WAIT 状态&#xff0c;为什么需要 TIME_WAIT 状态&a…

168张图,万字长文,手把手教你开发vue后台管理系统

“我报名参加金石计划1期挑战——瓜分10万奖池&#xff0c;这是我的第1篇文章&#xff0c;点击查看活动详情。” 我写这个项目的初衷一方面是为了记录自己自学 vue 的一个过程&#xff0c;另一方面是为了帮助小白快速掌握使用 vue 脚手架创建项目的一个完整过程。 本项目是一个…

STM32之ADC

目录 ADC介绍 ADC特性 ADC通道 ADC转换顺序 ADC触发方式 ADC转化时间 ADC转化模式 实验&#xff1a;使用ADC读取烟雾传感器的值 代码实现 ADC介绍 ADC 是什么&#xff1f; 全称&#xff1a; Analog-to-Digital Converter &#xff0c;指模拟 / 数字转换器 ADC的性能…

OpenCV安装配置教程VS2022(超级顺利)

前言 博主最开始没打算写这篇博客&#xff0c;只是想简单的在网上找一下最新版的OpenCV的安装配置教程&#xff0c;适用于VS2022的&#xff0c;但是我搜了一早上&#xff0c;也没搜到一个能让我成功安装配置的&#xff0c;看了很多很多文章&#xff0c;根本就没有几篇有用的。…

JavaEE三层架构

一. JavaEE三层架构 客户端&#xff1a;发送请求【http://ip:port/工程路径/资源路径】给服务器。 服务器&#xff1a;处理客户端的请求&#xff0c;把结果响应给客户端&#xff0c;由以下三部分组成 1.Web层&#xff08;SpringMVC&#xff09;包括&#xff1a;①使用Servlet…

微服务架构统一异常监控Sentry

Sentry 基本介绍简单使用Java项目应用代码侵入&#xff08;不建议&#xff0c;耦合性大&#xff09;全局拦截器捕获&#xff08;建议&#xff09;Lockback.xml 配置&#xff08;建议&#xff09; 基本介绍 Sentry 是一个开源的实时错误报告工具&#xff0c;支持 web 前后端、移…

《从零开始读懂Web3》读后感 之 Web 3.0的前世今生

介绍什么是 Web 3.0 &#xff08;Web3&#xff09;&#xff1f;网络的演变为什么 Web 3.0 很重要&#xff1f;Web 3.0 将如何运作&#xff1f;Web 3.0 的主要功能和技术Web 3.0 用例和应用程序Web 3.0 的潜在好处是什么&#xff1f;Web 3.0 的潜在挑战是什么&#xff1f;Web 3.…

【3】使用YOLOv8训练自己的目标检测数据集-【收集数据集】-【标注数据集】-【划分数据集】-【配置训练环境】-【训练模型】-【评估模型】-【导出模型】

在自定义数据上训练 YOLOv8 目标检测模型的步骤可以总结如下 6 步&#xff1a; &#x1f31f;收集数据集&#x1f31f;标注数据集&#x1f31f;划分数据集&#x1f31f;配置训练环境&#x1f31f;训练模型&#x1f31f;评估模型 1. 收集数据集 随着深度学习技术在计算机视觉领…

Windows安装mysql详细步骤(通俗易懂,简单上手)

文章目录 【确认本地是否安装mysql】【下载mysql安装包】【添加配置文件并安装mysql】【修改mysql密码】【配置环境变量】【总结】 前期在windows电脑尝试安装mysql&#xff0c;经历诸多不顺&#xff0c;特把安装详细步骤以及遇见的问题记录下来&#xff0c;提供给有需者使用。…

Java-Web前后端交互实现登陆注册(附源码)

1.完成用户登录功能。 2.完成注册功能。 3.主体利用Maven导入java中的jar包&#xff0c;使用Servlet实现前后端交互&#xff0c;使用mybatis以及注解&#xff0c;mysql进行数据保存&#xff0c;Tomcat服务器进行开发。 效果图 项目结构 代码 mapper(相对应注解) package com.it…

前端搭建砸地鼠游戏(内附源码)

The sand accumulates to form a pagoda ✨ 写在前面✨ 功能介绍✨ 页面搭建✨ 样式设置✨ 逻辑部分✨ 完整代码 ✨ 写在前面 上周我们实通过前端基础实现了打字通&#xff0c;当然很多伙伴再评论区提出了想法&#xff0c;后续我们会考虑实现的&#xff0c;今天还是继续按照我…

微服务---Redis实用篇-黑马头条项目-优惠卷秒杀功能(使用redis的消息队列对秒杀进行异步优化)

微服务—Redis实用篇-黑马头条项目-优惠卷秒杀功能(使用redis的消息队列对秒杀进行异步优化) 1、Redis消息队列 1.1 Redis消息队列-认识消息队列 什么是消息队列&#xff1a;字面意思就是存放消息的队列。最简单的消息队列模型包括3个角色&#xff1a; 消息队列&#xff1a…

零基础学模拟电路--2.运算放大器的虚短和虚断

零基础学模拟电路–2.运算放大器的虚短和虚断 虚短&#xff1a; 虚短指在理想情况下&#xff0c;两个输入端的电位相等&#xff0c;就好像两个输入端短接在一起&#xff0c;但事实上并没有短接&#xff0c;称为“虚短”。 由虚短可得出正负输入端点位相等的结论。 虚断&…

如何在 VS Code 中编写、运行C语言程序 教程

本篇目录 前言 1.下载、安装VS Code 2.安装VS code中2个插件 3.下载minGW64 4.配置系统的环境变量 5.C语言配置 6.编写一个测试程序 7.可能存在的问题 总结 前言 折腾了好久&#xff0c;终于成功地实现了在VS Code中写C语言程序&#xff0c;于是发文分享一下我的经验。 要想…