效果图

整体思路:

  1. 实现图片的缩放和拖动;
  2. 在图片上方盖上中间镂空的半透明遮罩;
  3. 根据截取方框区域的图片。截图时,在方框区域将截取的图片绘制出来,然后使用wx.canvasToTempFilePath截取图片。

实现过程和遇到的问题:

  1. 实现缩放和拖动最初是使用touchStarttouhMove,处理触摸事件来实现图片的缩放和拖动,但是这样的实现在小程序上会很卡。后来无意中发现了小程序提供的movable-areamovable-view可以很方便地实现控件的拖动和缩放。
  2. 使用canvas绘制图片上方的半透明遮罩,最初我是把canvas放在movable-aread的上方,也就是在wxml文件中排在movable-area后面,这样会导致图片监听不了拖动事件和缩放事件。后来我将canvas前置,利用小程序中canvas在最上层的特点,实现遮罩,而又不影响图片的拖动和缩放。
  3. 使用给canvas设置rgb(0,0,0,0.5)来实现canvas的半透明,而不是使用其opacity属性。因为在小程序中实现截图,需要先通过canvas将图片绘制出来。如果使用opacity绘制出来的图片也是半透明的,由于尺寸的误差,canvas绘制的图片和后面显示的图片会出现重影,效果不佳。
  4. canvas不对底部栏覆盖。我这里是使用绝对定位,canvas相对屏幕底部120rpx,底部栏的高度正好也是120rpx。这里也可以使用canvasclearRect方法实现。
  5. canvas准确绘制要截取的图片。关键在于以下几个参数:源图片的大小尺寸,当前图片显示的尺寸、方框在显示的图片中的位置和大小。而方框在显示的图片中的位置又是通过方框和显示的图片分别相对于屏幕的位置得到。根据方框在显示图片中的位置和显示图片的尺寸求得一个比例,然后乘以源图片的位置,就拿到了方框在源图片中的位置和大小。
  6. 在实现原微信截图终端"还原"的问题,在小程序上缩放和拖动不能同时进行,一步到位。具体原因在代码注释中有解释。
  7. 解决截取图片拿不到文件的问题。重要的是要对canvas绘制图片加上监听,在canvas绘制图片完成后才提取图片文件。

依然存在的问题

  1. 由于js计算的尺寸上的误差或者其他未知原因,用canvas绘制的图片和显示的图片会有个垂直方向上的轻微偏移。所以仔细看在点击完成时会有一个错位移动的现象。这个误差怎么解决,希望有心的网友可以告诉我。
  2. 前面也提到了,点击“撤销”(微信里叫“还原”),只能分两步撤销拖动和缩放,回到原来的位置。
  3. 受限于movable-area图片拖动能超出边界的范围不多,这应该可以通过放大movable-area解决,不过这样的话后面的一系列定位算法都要更改。网友有需要的话可以自己实现。

核心代码

Page({data: {// movable-view左上角相对于movable-area的坐标x,y,// 由于movable-area左上角位于屏幕原点,因此x,y也是movable-view相对于屏幕的坐标x: 0,y: 0,scale: 1, // 用来控制moveable-view的缩放比例,回撤重置时需要src: {// 图片的源文件数据path: '',orientation: 'up',// width和height在最初时和屏幕尺寸做比较,做一个合适的缩放// 在截图的时候,计算方框在源图片的位置也需要用到width和heightwidth: 0,height: 0,ratio: 1, // 图片的长宽比,最开始需要根据ratio判断图片的形状是长图还是宽图,进行合适的居中放置},image: {// 最初图片在屏幕上显示的宽度和高度// 经过缩放后也是基于这两个尺寸计算新的尺寸initialWidth: 0,initialHeight: 0,// 控制最初图片在屏幕中的位置initialX: 0,initialY: 0,// 经过缩放移动后图片在屏幕中的位置// 截图时找方框在源图片中的位置,是基于屏幕坐标系,所以需要图片的当前位置curX: 0,curY: 0,// 图片当前的缩放比例,用于计算图片当前显示的尺寸大小curScale: 1},// 屏幕尺寸windowWidth和windowHeightwindowWidth: 0,windowHeight: 0,cropBorder: {// 截图的方框相对于屏幕中的位置x: 0,y: 0,// 截图方框的尺寸size: 0},},/*** 生命周期函数--监听页面加载*/onLoad: function(options) {let that = this;let _src = JSON.parse(options.data);_src.ratio = _src.height / _src.width;let systemInfo = wx.getSystemInfoSync();// 屏幕内框范围(这里定义左右边界30,上下边际50)// 但是因为movable-view能超出movable-area的范围太小,为了尽量放大图片的活动范围,这里取消了这个边界// let borderWidth = systemInfo.windowWidth - 2 * 30// let borderHeight = systemInfo.windowHeight - 2 * 50let _image = that.data.imageif (_src.width > systemInfo.windowWidth && _src.height > systemInfo.windowHeight) {// 如果图片尺寸大于屏幕尺寸,需要缩放if (_src.ratio > 1) {// 如果是长图,按照宽度进行缩放_image.initialWidth = systemInfo.windowWidth// >> 0用来取整_image.initialHeight = (_src.ratio * systemInfo.windowWidth) >> 0} else {// 如果是宽图,按照高度进行缩放_image.initialWidth = systemInfo.windowHeight,_image.initialHeight = (systemInfo.windowHeight / _src.ratio) >> 0}} else {_image.initialWidth = _src.width_image.initialHeight = _src.height}// 控制图片在屏幕居中_image.initialX = (systemInfo.windowWidth - _image.initialWidth) >> 1_image.initialY = (systemInfo.windowHeight - _image.initialHeight) >> 1console.log(JSON.stringify(_image))// 定义方框的位置和尺寸let _cropBorder = {}_cropBorder.size = systemInfo.windowWidth * 0.9_cropBorder.x = (systemInfo.windowWidth - _cropBorder.size) >> 1_cropBorder.y = (systemInfo.windowHeight - _cropBorder.size) >> 1that.setData({src: _src,image: _image,cropBorder: _cropBorder,windowWidth: systemInfo.windowWidth,windowHeight: systemInfo.windowHeight})},/*** 移动movable-view的回调*/onChange(e) {console.log(e.detail)let that = this;that.setData({'image.curX': e.detail.x,'image.curY': e.detail.y})},/*** 缩放moveable-view的回调*/onScale(e) {console.log(e.detail)let that = this;that.setData({'image.curX': e.detail.x,'image.curY': e.detail.y,'image.curScale': e.detail.scale})},/*** 生命周期函数--监听页面初次渲染完成*/onReady: function() {let that = this;let cropBorder = that.data.cropBorderthis.context = wx.createCanvasContext('myCanvas')// 设置背景黑色透明度0.5,不要使用opacity,会导致后期截出来的图片也是半透明this.context.setFillStyle('rgba(0,0,0,0.5)')this.context.fillRect(0, 0, that.data.windowWidth, that.data.windowHeight)// 挖出来一个方框,这个方框区域就是全透明了this.context.clearRect(cropBorder.x, cropBorder.y, cropBorder.size, cropBorder.size)// 画方框的外框this.context.setStrokeStyle('white')// 往外画大一圈,这样在canvas上填充图片的时候框线就不会变细啦this.context.strokeRect(cropBorder.x - 1, cropBorder.y - 1, cropBorder.size + 2, cropBorder.size + 2)this.context.draw()},/*** 取消截图*/cancel(event) {wx.navigateBack()},/*** 回撤,这地方有个问题:* 本来是想图片一次性移动和缩放回到最初的位置和大小,但是发现点击第一次只能实现图片移动到初始点* 点击第二次才是缩放到初始大小* 后来发现小程序移动有一个过程,会不停地回调onChange(),* 如果一定要实现,只能是当onChange中x,y归0之后再控制缩放,* 但是x和y是浮点数,零判断不精确而且影响性能,所以放弃了。*/reset(event) {let that = this;that.setData({scale: 1,x: 0,y: 0,})},/*** 点击完成的回调* 完成截图,回传图片到上一页*/complete(event) {let that = thislet src = that.data.srcconsole.log(src)let cropBorder = this.data.cropBorderlet image = that.data.image// 当前图片显示的大小let curImageWidth = image.initialWidth * image.curScalelet curImageHeight = image.initialHeight * image.curScale// 将方框位置换算到源图片中的位置srcX,srcYlet srcX = (cropBorder.x - image.curX) / curImageWidth * src.width// canvas的height是100%,而bottom是120rpx,因此canvas的位置不是在原点,需要减去这个120rpx// 置于这里为什么要有个120,因为底部栏也是透明的,但字是亮的,我想呈现底部栏在上方不被遮罩挡住的效果// 经过测试,这里-4可以解决canvas绘制的图片向上偏移的问题,为什么是-4我也不知道,与边框的厚度有关?let srcY = (cropBorder.y - image.curY - 120 / 750 * that.data.windowWidth-4) / curImageHeight * src.height// 方框区域映射到源图片中的尺寸let srcWidth = cropBorder.size / curImageWidth * src.widthlet srcHeight = cropBorder.size / curImageHeight * src.heightconsole.log('srcX = ' + srcX + ', srcY = ' + srcY + ', srcWidth = ' + srcWidth + ', srcHeight = ' + srcHeight + ', cropX = ' + cropBorder.x + ', cropY = ' + cropBorder.y + ', cropSize = ' + cropBorder.size)// 绘制图片不要透明啦,不然会看到重影this.context.setFillStyle('rgba(0,0,0,1)')// 鉴于尺寸的精确度,方框内图片的覆盖在y方向会有微微的偏移,// 但是一旦截图就返回上一页了,强迫症患者没有后悔的余地。this.context.drawImage(src.path, srcX, srcY, srcWidth, srcHeight, cropBorder.x, cropBorder.y, cropBorder.size, cropBorder.size)// 这里绘图一定要有回调,不然图片还没绘制完成就截图那就GG了this.context.draw(true, function(res) {wx.canvasToTempFilePath({canvasId: 'myCanvas',x: cropBorder.x,y: cropBorder.y,width: cropBorder.size,height: cropBorder.size,destWidth: cropBorder.size,destHeight: cropBorder.size,fileType: 'jpg',success: function(data) {console.log(data)// 将图片回传到上一页var pages = getCurrentPages();if (pages.length > 1) {var prePage = pages[pages.length - 2];prePage.setData({newAvatar: data.tempFilePath})}wx.navigateBack()},fail: function(err) {console.log(err)}})})},
})

源代码

https://github.com/sheaye/wx-avatar-cropper

微信小程序仿照微信拖动缩放图片和截取头像相关推荐

  1. 在c 语言里如何制作滑动图片大小,[教程]教你在微信小程序中实现手势缩放图片...

    在小程序中,image 组件的 mode 有 12 种,其中只有三种是缩放模式.而在这三种之中,只有 aspectFit 模式可以等比例缩放图片,并显示完整的图片.此模式是保证图片长边完整地显示出来, ...

  2. 微信小程序使用wxParse,解决图片显示路径问题

    微信小程序使用wxParse,解决图片显示路径问题 参考文章: (1)微信小程序使用wxParse,解决图片显示路径问题 (2)https://www.cnblogs.com/zxf100/p/100 ...

  3. imagepreview使用案例_微信小程序wx.previewImage预览图片实例详解

    一.小知识 二.例子 1.wxml 2.wxss .container { box-sizing:border-box; padding:20px; } .previewimg{ float:left ...

  4. 微信小程序 长按图片不出现菜单_微信小程序实现长按删除图片的示例

    说明 最近在学小程序,遇到长按图片删除的问题,特此记录,记录自己的成长轨迹 需求: 长按删除指定图片 需要解决的问题 长按事件如何表示出来? 如何获取当前长按元素的下标? 如何删除元素? 解决办法 长 ...

  5. 微信小程序使用image组件显示图片的方法

    本文实例讲述了微信小程序使用image组件显示图片的方法.分享给大家供大家参考,具体如下: 1.效果展示 2.关键代码 ① index.wxml 代码如下: <image style=" ...

  6. 微信小程序,动态改变背景图片

    前言,作为开发新人,最近在学习微信小程序,期间遇到不少问题,对问题的解决的方法进行记录,以供学习之用. 关于微信小程序的背景图片问题, 1.微信小程序不能使用本地的图片设置背景图片,即 <vie ...

  7. 微信小程序:想要点击图片时进行一个放大的功能,写完之后可以正常显示,但点击时图片加载不出来,在此想问一下各路大神有没有解决办法

    微信小程序:想要点击图片时进行一个放大的功能,写完之后可以正常显示,但点击时图片加载不出来,在此想问一下各路大神有没有解决办法 wxml: 在这里插入代码片 <view class='zhanp ...

  8. 微信小程序使用image组件时图片不显示的一个解决方法

    第一次写博客: 图片链接可正常访问,但是使用组件时,图片不显示,偶然之间尝试到了一种解决方法,我使用的是微信小程序云端,读取本地图片时使用了具体的路径,错误用法如下 如果按照指示\ 一级一级选下来,导 ...

  9. 微信小程序封装懒加载图片

    微信小程序封装懒加载图片 js /components/LazyImage/index.js // components/LazyImage/index.js Component({/*** 组件的属 ...

最新文章

  1. 【记录一个问题】cuda核函数可能存在栈溢出,导致main()函数退出后程序卡死30秒CUDA...
  2. 程序员用5分钟,把一个400多MB的苹果安装包削掉了187MB
  3. c# 建立到数据源的连接 以及获取项目配置文件的属性
  4. mysql 魔乐_MLDN 李兴华 魔乐科技网上最全笔记
  5. ISE简介及其下载 安装 和谐 与 卸载
  6. [Java基础]自动装箱和拆箱
  7. 线性表的顺序表示和实现(严蔚敏版)
  8. 咏南中间件+开发框架中秋国庆大促
  9. 部门研发linux sysfs (2)部门研发
  10. linux intel显卡驱动下载,下载 英特尔®显卡 BETA WSL Windows® 10 DCH 驱动程序
  11. EasyUI 中 Combobox里的onChange和onSelect事件的区别
  12. android_基础_修改系统背景(状态栏颜色、导航栏颜色、标题栏颜色等等)
  13. WEB安全(十六)单点登录的基本实现
  14. 彻底理解Runnable与Thread,真的有区别吗?
  15. php 百度收录api_百度移动资源天级收录API提交-在线工具
  16. python数据分析之pandas数据选取:df[] df.loc[] df.iloc[] df.ix[] df.at[] df.iat[]
  17. staruml 时序图操作
  18. 配置SQLServer允许远程连接
  19. linux prom命令解释,eeprom 执行boot PROM命令
  20. 黑马程序员--网络编程知识点总结

热门文章

  1. 前端数据可视化之高德地图
  2. [Database] 关系型数据库中的MVCC是什么?怎么理解?原理是什么?MySQL是如何实现的?
  3. day08学习 Python文件函数、CSV文件读写
  4. MISC常用工具(会接着补)
  5. flink jdbc connector支持clickhouse
  6. mysql association_MyBatis的association示例——MyBatis学习笔记之三
  7. c语言中源文件未编译是什么,源文件未编译什么意思
  8. Java开源项目管理工具大全
  9. 机器周期、振荡周期、时钟周期、状态周期???
  10. 录制iPhone屏幕