博客
关于我
微信小程序可移动缩放图片裁剪框
阅读量:301 次
发布时间:2019-03-01

本文共 14820 字,大约阅读时间需要 49 分钟。

效果预览

在这里插入图片描述

提示:模拟器下由于微信自己的原因,裁剪按钮会被canvas覆盖,真机测试正常。

前言

图片裁剪框应用很普遍,也有很多成熟的组件,但是很多组件都是固定裁剪框的位置,通过移动图片来完成图片指定区域的裁剪。这种方法比较容易实现,但是同时存在灵活性不高,裁剪区域选择不精确,图片尺寸不能灵活改变的局限性。下面介绍一种通过使用微信小程序canvas来实现一个可移动缩放的图片裁剪框的方法。

技术要点分析

使用canvas组件实现图片裁剪框涉及以下技术内容:

(1)使用canvas绘制图片
(2)绘制图片裁剪框
(3)裁剪框四个缩放点位置计算
(4)裁剪框的移动和缩放
(5)防止裁剪框超出屏幕
(6)在裁剪框发生变化时重绘图片和裁剪框
(7)生成指定图片区域的裁剪图片
以上这些技术的实现需要进行一定的几何计算,在这里,裁剪框的原点坐标是左上角缩放点的坐标,另外三个缩放点可以根据裁剪框的宽度和高度进行坐标偏移得到。

具体实现

(0)页面数据结构

data:{   // 是否打开相机拍照  open_camera: true,  // 剪切图片的canvas上下文  cut_image_canvas: {   },  // canvas实例  mycanvas: {   },  // 图片对象  image_obj: {   },  // 屏幕宽度和高度  window_width: 0,  window_heigt: 0,  // 画布宽高  cut_img_canvas_w: 0,  cut_img_canvas_h: 0,  // 裁剪区域  cut_area: {       // 裁剪区域左上角离屏幕原点坐标    x: 0,    y: 0,    // 裁剪宽高    cut_width: 0,    cut_height: 60,    // 裁剪区域边框颜色    cut_area_color: 'red',  },  // 屏幕像素比  pixelRatio: 1,  // 裁剪区域移动和缩放时上一个触摸点坐标  // 用来计算移动距离和移动方向  last_touches_x: 0,  last_touches_y: 0,  // 裁剪框变换类型,可选值有:  // move,left_up_scale,left_down_scale,  // right_up_scale,right_down_scale  cut_area_change_status: 'move',  // 裁剪好的图片临时文件链接  cut_image: ''}

(1)wxml,wxss和页面数据初始化

图片裁剪主要涉及的系统参数有三个:屏幕窗口宽度(windowWidth),窗口高度(windowHeight,除去状态栏和小程序导航栏剩下的高度)和像素比例(pixelRatio,定义参考微信官方文档:)

注意事项: 推荐在pageonReady或者自定义组件的ready生命周期中进行初始化。
wxml:

裁剪
拍照

wxss

/* 拍照按钮 和图片裁剪按钮*/.take_photo  {     width: 240rpx;  height: 96rpx;  background: white;  z-index: 1000;  display: flex;  flex-direction: row;  align-content: center;  align-items: center;  justify-content: space-around;  justify-items: center;  border-radius: 10rpx;  background: rgba(0, 0, 0, 0.4);  color: white;  position: fixed;  bottom: 8%;  left: 35%;  text-align: center;  font-size: 30rpx;}/* 拍照和图片裁剪按钮点击态 */.take_photo_hover{     transform: scale(0.9);}

js数据初始化

//在page中用时放到onReady方法中ready: function() {         const that = this      // 获取屏幕宽高和像素比      wx.getSystemInfo({           success: function(res) {             that.data.window_heigt = res.windowHeight          that.data.window_width = res.windowWidth          that.data.pixelRatio = res.pixelRatio        },      })    }

(2)待裁剪图片生成

微信小程序上的图片来源基本就两种,一种是从相册选取或者相机拍摄,另一种是网络图片,一般情况下图片裁剪主要在用户拍摄并上传图片的时候出现,因此这里使用的待裁剪图片由相机拍摄生成,代码如下:

/**     * 拍照     */    take_photo: function() {         const that = this      //相机上下文对象      const ctx = wx.createCameraContext()      ctx.takePhoto({           quality: 'high',        success: (res) => {           //loading,防止闪屏          wx.showLoading({               title: '',          })           // 隐藏相机          that.setData({               open_camera: false          })          //创建图片裁剪画布并绘制图片          that.create_cut_image_canvas(res.tempImagePath)        }      })    },

微信官方文档:

(3)创建canvas绘制图片和裁剪框

绘制图片到canvas使用的APIdrawImage,绘制图片分为两步:

1.根据相机生成的临时图片链接生成要绘制的图片对象。
2.绘制图片
具体实现如下:

/** * 创建图片裁剪画布和图片裁剪框 * image_url:要绘制的图片,只接受微信小程序临时文件链接 * 网络图片需要先下载到本地 * */create_cut_image_canvas:function(image_url) {     const that=this  // 在page中使用时应改为wx.createSelectorQuery()  const query = that.createSelectorQuery()  query.select('#cut_image_canvas')    .fields({         node: true,      size: true    })    .exec((res) => {         //获取画布实例      const canvas = res[0].node      that.data.mycanvas = canvas      //获取画布上下文      that.data.cut_image_canvas = canvas.getContext('2d')      let w = 0      let h = 0      // 根据要绘制的图片调整画布宽高      wx.getImageInfo({           src: image_url,        success: function(res) {             // 画布宽度          canvas.width = that.data.window_width;          w = that.data.window_width          // 画布高度          canvas.height = that.data.window_width / res.width * res.height          h = canvas.height          // 预先设置裁剪区域宽度为屏幕宽度的80%          that.data.cut_area.cut_width = 0.8 * w          // 预先设定的裁剪区域左上角顶点位置          that.data.cut_area.x = (w - that.data.cut_area.cut_width) / 2          that.data.cut_area.y = (h - that.data.cut_area.cut_height) / 2          // 设置画布宽高          that.setData({               cut_img_canvas_h: h,            cut_img_canvas_w: w          })          // 绘制图片          that.data.image_obj = canvas.createImage();          that.data.image_obj.src = image_url          that.data.image_obj.onload = () => {               that.data.cut_image_canvas.drawImage(that.data.image_obj, 0, 0, w,             h)            // 创建图片裁剪区域            that.create_cut_area()          }        }      })    })}

绘制裁剪框

/** * 绘制裁剪区域 * that:自定义组件实例或page实例,即this */create_cut_area:function() {     const that=this  // 创建预先选中的图片裁剪区域  that.data.cut_image_canvas.strokeStyle = that.data.cut_area.cut_area_color  // 裁剪区域边界线  that.data.cut_image_canvas.strokeRect(that.data.cut_area.x, that.data.cut_area.y, that.data.cut_area.cut_width, that.data.cut_area.cut_height)  // 裁剪区域边界的缩放圆点  // 左上角外圆点  that.data.cut_image_canvas.beginPath()  that.data.cut_image_canvas.arc(that.data.cut_area.x, that.data.cut_area.y, 5 * that.data.pixelRatio, 0, 2 * Math.PI)  that.data.cut_image_canvas.fillStyle = 'white'  that.data.cut_image_canvas.fill()  // 左上角内圆点  that.data.cut_image_canvas.beginPath()  that.data.cut_image_canvas.arc(that.data.cut_area.x, that.data.cut_area.y, 2.5 * that.data.pixelRatio, 0, 2 * Math.PI)  that.data.cut_image_canvas.fillStyle = '#1296db'  that.data.cut_image_canvas.fill()  // 左下角外圆点  that.data.cut_image_canvas.beginPath()  that.data.cut_image_canvas.arc(that.data.cut_area.x, that.data.cut_area.y + that.data.cut_area.cut_height, 5 * that.data.pixelRatio, 0, 2 * Math.PI)  that.data.cut_image_canvas.fillStyle = 'white'  that.data.cut_image_canvas.fill()  // 左下角内圆点  that.data.cut_image_canvas.beginPath()  that.data.cut_image_canvas.arc(that.data.cut_area.x, that.data.cut_area.y + that.data.cut_area.cut_height, 2.5 * that.data.pixelRatio, 0, 2 * Math.PI)  that.data.cut_image_canvas.fillStyle = '#1296db'  that.data.cut_image_canvas.fill()  // 右下角外圆点  that.data.cut_image_canvas.beginPath()  //绘制圆点,半径乘以pixelRatio的目的是在不同屏幕下保持圆点大小一致  that.data.cut_image_canvas.arc(that.data.cut_area.x + that.data.cut_area.cut_width, that.data.cut_area.y + that.data.cut_area.cut_height, 5 * that.data.pixelRatio, 0, 2 * Math.PI)  that.data.cut_image_canvas.fillStyle = 'white'  that.data.cut_image_canvas.fill()  // 右下角内圆点  that.data.cut_image_canvas.beginPath()  that.data.cut_image_canvas.arc(that.data.cut_area.x + that.data.cut_area.cut_width, that.data.cut_area.y + that.data.cut_area.cut_height, 2.5 * that.data.pixelRatio, 0, 2 * Math.PI)  that.data.cut_image_canvas.fillStyle = '#1296db'  that.data.cut_image_canvas.fill()  // 右上角外圆点  that.data.cut_image_canvas.beginPath()  that.data.cut_image_canvas.arc(that.data.cut_area.x + that.data.cut_area.cut_width, that.data.cut_area.y, 5 * that.data.pixelRatio, 0, 2 * Math.PI)  that.data.cut_image_canvas.fillStyle = 'white'  that.data.cut_image_canvas.fill()  // 右上角内圆点  that.data.cut_image_canvas.beginPath()  that.data.cut_image_canvas.arc(that.data.cut_area.x + that.data.cut_area.cut_width, that.data.cut_area.y, 2.5 * that.data.pixelRatio, 0, 2 * Math.PI)  that.data.cut_image_canvas.fillStyle = '#1296db'  that.data.cut_image_canvas.fill()}

(4)计算四个缩放点位置

裁剪框缩放和移动时需要进行重绘,因此需要不断计算新的缩放点位置,计算实现如下:

/**     * 获取裁剪框四个缩放点位置     */    get_scale_point_location: function() {         const that = this      return {           // 裁剪框左上角缩放点位置        left_up_point_x: that.data.cut_area.x,        left_up_point_y: that.data.cut_area.y,        // 左下角缩放点位置        left_down_point_x: that.data.cut_area.x,        left_down_point_y: that.data.cut_area.y + that.data.cut_area.cut_height,        // 右下角缩放点位置        right_down_point_x: that.data.cut_area.x + that.data.cut_area.cut_width,        right_down_point_y: that.data.cut_area.y + that.data.cut_area.cut_height,        // 右上角缩放点位置        right_up_point_x: that.data.cut_area.x + that.data.cut_area.cut_width,        right_up_point_y: that.data.cut_area.y      }    },

(5)辨别裁剪框移动和缩放手势

创建完裁剪框之后,接下来需要给裁剪框添加缩放和移动响应,裁剪框缩放和移动的原理是首先根据canvas 使用bindtouchstart绑定的get_cut_area_change_status方法,计算第一次触摸屏幕的位置,根据触点位置判断接下来裁剪框是要进行缩放(触点离缩放点很近)还是移动(触点离缩放点远但是落在裁剪框内),然后根据使用bindtouchmove绑定的cut_area_move_and_scale方法计算触摸移动距离,然后根据之前的判断结果进行响应的缩放和移动。get_cut_area_change_status方法如下:

/** * 根据触点位置辨别是移动还是缩放图片裁剪框 * e:点击事件 */get_cut_area_change_status:function (e) {     const that=this  // 触摸点位置  let x = e.touches[0].x  let y = e.touches[0].y  // 复位裁剪框变化状态  that.data.cut_area_change_status = ''  // 缩放感应区域半径,触点落入该区域响应缩放  //目的是防止缩放点太小,缩放反应迟钝  let scale_reponse_radius = 30  // 记录当前坐标,用来计算触摸移动距离  that.data.last_touches_x = x  that.data.last_touches_y = y  // 获取四个缩放点位置  const scale_point_location = that.get_scale_point_location()  // 裁剪框左上角缩放点位置  let left_up_point_x = scale_point_location.left_up_point_x  let left_up_point_y = scale_point_location.left_up_point_y  // 左下角缩放点位置  let left_down_point_x = scale_point_location.left_down_point_x  let left_down_point_y = scale_point_location.left_down_point_y  // 右下角缩放点位置  let right_down_point_x = scale_point_location.right_down_point_x  let right_down_point_y = scale_point_location.right_down_point_y  // 右上角缩放点位置  let right_up_point_x = scale_point_location.right_up_point_x  let right_up_point_y = scale_point_location.right_up_point_y  // 判断是否是在移动裁剪框  if ((left_up_point_x + scale_reponse_radius) < x && x < (right_down_point_x - scale_reponse_radius) && left_up_point_y < y && y < right_down_point_y) {       that.data.cut_area_change_status = 'move'  }  // 按住左上角缩放  if (Math.sqrt(Math.pow(x - left_up_point_x, 2) + Math.pow(y - left_up_point_y, 2)) < scale_reponse_radius) {       that.data.cut_area_change_status = 'left_up_scale'  }  // 按住左下角缩放  if (Math.sqrt(Math.pow(x - left_down_point_x, 2) + Math.pow(y - left_down_point_y, 2)) < scale_reponse_radius) {       that.data.cut_area_change_status = 'left_down_scale'  }  // 按住右上角缩放  if (Math.sqrt(Math.pow(x - right_up_point_x, 2) + Math.pow(y - right_up_point_y, 2)) < scale_reponse_radius) {       that.data.cut_area_change_status = 'right_up_scale'  }  // 按住右下角缩放  if (Math.sqrt(Math.pow(x - right_down_point_x, 2) + Math.pow(y - right_down_point_y, 2)) < scale_reponse_radius) {       that.data.cut_area_change_status = 'right_down_scale'  }}

缩放感应区域和移动感应区域划分如下:

在这里插入图片描述
红圈中就是缩放感应区域,矩形中除去红圈剩下的区域就是移动感应区域。

(6)canvas重绘

canvas重绘的目的是在裁剪框发生变化时,更新canvas,防止裁剪框绘制发生重叠。实现如下:

/**     * 画布重绘     */    redraw: function(mode = 0) {         const that = this      // 清空画布      that.data.cut_image_canvas.clearRect(0, 0, that.data.cut_img_canvas_w, that.data.cut_img_canvas_h)      // 重绘图片      that.data.cut_image_canvas.drawImage(that.data.image_obj, 0, 0, that.data.cut_img_canvas_w, that.data.cut_img_canvas_h)      // 重绘裁剪框      // mode=0时重绘裁剪框      // mode=1时不绘制裁剪框,防止切图时裁剪框被切入      if (mode == 0) {           that.create_cut_area()      }    },

(7)防止裁剪框超出屏幕边界

裁剪框进行移动和缩放时,可能会超出屏幕,因此需要根据四个缩放点的位置判断是否超出屏幕,在超出屏幕时阻止裁剪框缩放和移动,实现如下:

/** * 防止裁剪框超出屏幕 * that:自定义组件或者页面实例,即this */watch_cut_area_overflow:function () {     const that=this  // 距离屏幕边界裕度  let margin = 5 * that.data.pixelRatio  // 获取四个缩放点位置  const scale_point_location = that.get_scale_point_location()  // 裁剪框左上角缩放点位置  let left_up_point_x = scale_point_location.left_up_point_x  let left_up_point_y = scale_point_location.left_up_point_y  // 左下角缩放点位置  let left_down_point_x = scale_point_location.left_down_point_x  let left_down_point_y = scale_point_location.left_down_point_y  // 右下角缩放点位置  let right_down_point_x = scale_point_location.right_down_point_x  let right_down_point_y = scale_point_location.right_down_point_y  // 右上角缩放点位置  let right_up_point_x = scale_point_location.right_up_point_x  let right_up_point_y = scale_point_location.right_up_point_y  // 裁剪框左边超出屏幕  if (left_up_point_x < margin) {       return 1  }  //裁剪框右边超出屏幕  if (right_down_point_x > (that.data.window_width - margin)) {       return 1  }  // 裁剪框上边超出屏幕  if (left_up_point_y < margin) {       return 1  }  //裁剪框下边超出屏幕  if (right_down_point_y > (that.data.window_heigt - margin)) {       return 1  }  return 0}

(8)裁剪框移动和缩放

做完(4)——(7)所述的工作,接下来可以实现裁剪框的移动和缩放和响应了。具体实现如下:

/** * 裁剪框移动和缩放 * e:微信小程序点击事件 */cut_area_move_and_scale:function(e) {     const that=this  // 当前触点x和y坐标  let touch_x = e.touches[0].x  let touch_y = e.touches[0].y  // 坐标变化量  let dx = touch_x - that.data.last_touches_x  let dy = touch_y - that.data.last_touches_y  // 按住左上角缩放点缩放  if (that.data.cut_area_change_status == "left_up_scale" || that.data.cut_area_change_status == "left_down_scale") {       // 更新裁剪框高度    that.data.cut_area.cut_height += -dy    that.data.cut_area.cut_width += -dx    // 更新左上角坐标    that.data.cut_area.x += dx    that.data.cut_area.y += dy    // 裁剪框超出屏幕    if (that.watch_cut_area_overflow()) {         // 超出屏幕,撤销变化      that.data.cut_area.cut_height -= -dy      that.data.cut_area.cut_width -= -dx      that.data.cut_area.x -= dx      that.data.cut_area.y -= dy      return;    }    // 重绘画布    that.redraw()  }  // 按住其他缩放点缩放  if (that.data.cut_area_change_status == "right_up_scale" || that.data.cut_area_change_status == "right_down_scale") {       // 更新裁剪框高度    that.data.cut_area.cut_height += dy    that.data.cut_area.cut_width += dx    // 裁剪框超出屏幕    if (that.watch_cut_area_overflow()) {         // 超出屏幕,撤销坐标变化      that.data.cut_area.cut_width -= dx      that.data.cut_area.cut_height -= dy      return;    }    // 重绘画布    that.redraw()  }  // 整体移动裁剪框  if (that.data.cut_area_change_status == "move") {       // 更新裁剪框左上角坐标量    that.data.cut_area.x += dx    that.data.cut_area.y += dy    // 裁剪框超出屏幕    if (that.watch_cut_area_overflow()) {         // 超出屏幕,撤销坐标增量      that.data.cut_area.x -= dx      that.data.cut_area.y -= dy      return;    }    // 重绘画布    that.redraw()  }  // 更新点坐标  that.data.last_touches_x = touch_x  that.data.last_touches_y = touch_y}

(9)生成裁剪后的图片及图片预览

最后,还需要把裁剪的图片区域导出成图片,代码实现如下:

/**     * 图片裁剪     */    cut_image: function() {         const that = this      //此处重绘的目的是隐藏裁剪框,      //防止裁剪框被切入图片      that.redraw(1)      wx.canvasToTempFilePath({           // 除以像素比例,因为切图时使用的是相对像素        x: that.data.cut_area.x / that.data.pixelRatio,        y: that.data.cut_area.y / that.data.pixelRatio,        //必须取整,否则安卓下回切图失败        width: Math.round(that.data.cut_area.cut_width / that.data.pixelRatio),        height: Math.round(that.data.cut_area.cut_height / that.data.pixelRatio),        canvas: that.data.mycanvas,        filetype: 'png',        success: function(res) {             console.log("裁剪的图片", res.tempFilePath)          // 预览剪切好的图片          that.setData({               cut_image: res.tempFilePath          })        },        fail: function(err) {             console.log("裁剪图片失败", err)          wx.showModal({               title: '错误',            content: '剪切图片失败',          })        }      }, this)    }

集成到项目

上述代码已经封装成一个自定义组件,方便集成到项目,如果想要直接在page中使用,直接复制以上代码到相应的page中即可,自定义组件下载地址:

链接:https://pan.baidu.com/s/1p3o1AM5ZOE6y_hnkHNFShw
提取码:wbau

转载地址:http://uhet.baihongyu.com/

你可能感兴趣的文章
MTK Android 如何获取系统权限
查看>>
MySQL - 4种基本索引、聚簇索引和非聚索引、索引失效情况、SQL 优化
查看>>
MySQL - ERROR 1406
查看>>
mysql - 视图
查看>>
MySQL - 解读MySQL事务与锁机制
查看>>
MTTR、MTBF、MTTF的大白话理解
查看>>
mt_rand
查看>>
mysql -存储过程
查看>>
mysql /*! 50100 ... */ 条件编译
查看>>
mudbox卸载/完美解决安装失败/如何彻底卸载清除干净mudbox各种残留注册表和文件的方法...
查看>>
mysql 1264_关于mysql 出现 1264 Out of range value for column 错误的解决办法
查看>>
mysql 1593_Linux高可用(HA)之MySQL主从复制中出现1593错误码的低级错误
查看>>
mysql 5.6 修改端口_mysql5.6.24怎么修改端口号
查看>>
MySQL 8.0 恢复孤立文件每表ibd文件
查看>>
MySQL 8.0开始Group by不再排序
查看>>
mysql ansi nulls_SET ANSI_NULLS ON SET QUOTED_IDENTIFIER ON 什么意思
查看>>
multi swiper bug solution
查看>>
MySQL Binlog 日志监听与 Spring 集成实战
查看>>
MySQL binlog三种模式
查看>>
multi-angle cosine and sines
查看>>