目录

前言

实现过程


前言

本文介绍使用微信小程序API+canvas来实现图片的可拖动、放大、缩小和旋转,并可对选中的图片进行不同效果的滤镜和不同形状的切图,且可对最终效果进行保存到本地。

最终效果:

实现过程

1. 文件index.wxml和index.wxss代码如下,这一块比较简单,可自行查看,不做过多分析:

<view class='contentWarp'><block wx:for="{{itemList}}" wx:key="id"><view class='touchWrap' style='transform: scale({{item.scale}});top:{{item.top}}px;left:{{item.left}}px; z-index:{{item.active?100:1}}'><view class='imgWrap {{item.active? "touchActive":""}}' style="transform: rotate({{item.angle}}deg); border: {{item.active?4*item.oScale:0}}rpx #fff dashed;"><image src='{{item.image}}' data-id='{{item.id}}' style='width:{{item.width}}px;height:{{item.height}}px;' bindtouchstart='WraptouchStart' bindtouchmove='WraptouchMove' bindtouchend='WraptouchEnd'  mode="widthFix"></image><image class='x' src='../../images/del.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtap='deleteItem'></image><image class='o' src='../../images/scale.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtouchstart='oTouchStart' bindtouchmove='oTouchMove' bindtouchend='WraptouchEnd'></image></view></view></block>
</view>
<!-- 右下角操作按钮 -->
<view class="operation-buttons"><image src="../../images/upload.png" bindtap="uploadImg"></image><image src="../../images/fliters.png" bindtap="toggleFliters"></image><image src="../../images/shapes.png" bindtap="toggleShapes"></image><image src="../../images/synthesis.png" bindtap="openMask"></image>
</view>
<!-- 各种过滤效果 -->
<view class="fliters" hidden="{{!showFliters}}"><block wx:for="{{fliters}}" wx:key="id"><image data-fliter="{{item.fliter}}" src="{{item.src}}" bindtap="imgEffects"></image></block>
</view>
<!-- 各种形状 -->
<view class="shapes" hidden="{{!showShapes}}"><block wx:for="{{shapes}}" wx:key="id"><image data-shape="{{item.shape}}" src="{{item.src}}" bindtap="imgEffects"></image></block>
</view>
<!-- 保存显示效果图 -->
<view class='canvasWrap' hidden="{{!showCanvas}}"><image class="resImg" bindlongtap="saveImg" src="{{canvasTemImg}}" mode="widthFix"></image><view class='btn_view'><button bindtap='saveImg'>保存到手机</button><button bindtap='disappearCanvas'>关闭</button></view>
</view>
<!-- 画布 -->
<canvas class='maskCanvas' canvas-id="maskCanvas" style='width:{{canvasWidth}}px; height:{{canvasHeight}}px;'></canvas>
/**index.wxss**/
.contentWarp {position: absolute;width: 100%;height: 100%;top: 0;left: 0;bottom: 0;right: 0;margin: auto;
}.touchWrap {transform-origin: center;position: absolute;z-index: 100;
}.imgWrap {box-sizing: border-box;width: 100%;transform-origin: center;float: left;border: 5rpx transparent dashed;
}.imgWrap image {float: left;
}.touchActive .x {display: block;
}.touchActive .o {display: block;
}.x {position: absolute;top: -25rpx;left: -25rpx;z-index: 500;display: none;width: 50rpx;height: 50rpx;overflow: hidden;font-weight: bold;color: #d1e3f1;
}.o {position: absolute;bottom: -25rpx;right: -25rpx;width: 50rpx;height: 50rpx;text-align: center;display: none;overflow: hidden;font-weight: bold;color: #d1e3f1;
}.active {background-color: rgb(78, 114, 151);
}.active view {border: none;
}.touchActive {z-index: 400;
}.operation-buttons {position: absolute;bottom: 100rpx;right: 20rpx;display: flex;flex-direction: column;z-index: 101;
}.operation-buttons image {width: 100rpx;height: 100rpx;margin-top: 40rpx;
}.canvasWrap {position: absolute;width: 100%;height: 100%;top: 0;left: 0;background-color: rgba(0, 0, 0, 0.6);z-index: 999;text-align: center;
}.maskCanvas {position: absolute;left: -200%;top: 0;
}.btnView view {padding-bottom: 20rpx;
}.hand {position: absolute;left: 100rpx;right: 0;margin: auto;z-index: 100;
}.getUserInfoBtn {position: initial;border: none;background-color: none;
}.getUserInfoBtn::after {border: none;
}.btn_view {display: flex;padding: 20rpx;
}.btn_view button {width: 210rpx;font-size: 28rpx;background-color: #eb4985;color: #fff;line-height: 80rpx;
}.resImg {width: 75%;margin-top: 10px;
}/* 特效样式 */
.fliters {display: flex;flex-direction: column;position: absolute;bottom: 382rpx;right: 120rpx;z-index: 201;
}.shapes {display: flex;flex-direction: column;position: absolute;bottom: 242rpx;right: 120rpx;z-index: 201;
}.fliters image, .shapes image {width: 60rpx;height: 60rpx;border: 2rpx solid #eb4985;
}

2. 文件index.js存放所有功能的逻辑代码,相对比较复杂,下面分开来分析几个重点方法:

1)方法uploadImg+setDropItem:获取上传图片的信息,跟设置的最大宽高进行判断(maxWidth, maxHeight),然后根据判断的结果进行缩放,避免大图溢出,且设置图片的地址、坐标、定位和是否选中等信息;用于后面功能使用,支持多图使用;

2)方法WraptouchStart+WraptouchMove:获取图片移动坐标和触发时坐标的差值,加上图片本来的坐标来实现移动效果,注意要把移动坐标赋值给触发时坐标(items[index].lx = e.touches[0].clientX),不然会导致移动有问题;

3)方法oTouchStart+oTouchMove:获取拖动后图片的半径跟触发时图片的半径的比值,再使用scale来实现缩放功能(items[index].disPtoO / items[index].r);获取触发时的图片角度+拖动后图片的角度,再使用rotate来实现旋转功能(items[index].angle = items[index].rotate);

4)方法imgEffects:调用滤镜方法util.imgFliters(详细可到https://jingyan.baidu.com/article/ed15cb1b9fd9bb1be3698183.html查看),根据设置的滤镜值,进行不同的滤镜处理;而调用形状方法util.imgShapes,根据设置的形状值,进行不同的切图效果;

5)方法synthesis:用来把所有图片的最终效果合成一个画布,用于保存图片到本地使用;

6)方法saveImg:把画布保存到本地相册。

let index = 0, // 当前点击图片的indexitems = [], // 图片数组信息itemId = 1, // 图片id,用于识别点击图片fliter = 'init', // 默认过滤类型(原图)shape = 'init'; // 默认形状(原图)
const hCw = 1.62; // 图片宽高比
const canvasPre = 1; // 展示的canvas占mask的百分比
const maskCanvas = wx.createCanvasContext('maskCanvas', this); // 创建 canvas 的绘图上下文 CanvasContext 对象const util = require('../../utils/util.js');
Page({/*** 页面的初始数据*/data: {itemList: [],showFliters: false, // 默认不显示过滤效果框showShapes: false, // 默认不显示形状效果框fliters: [{fliter: 'init',src: '../../images/init.jpg'}, {fliter: 'bw',src: '../../images/bw.jpg'}, {fliter: 'groundGlass',src: '../../images/groundGlass.jpg'}, {fliter: 'pictureStory',src: '../../images/pictureStory.jpg'}, {fliter: 'reminiscence',src: '../../images/reminiscence.jpg'}, {fliter: 'sketch',src: '../../images/sketch.jpg'}],shapes: [{shape: 'circle',src: '../../images/init.jpg'}, {shape: 'star',src: '../../images/init.jpg'}, {shape: 'irregularityHeart',src: '../../images/init.jpg'}, {shape: 'SudokuHeart',src: '../../images/init.jpg'}]},/*** 生命周期函数--监听页面加载*/onLoad: function(options) {items = this.data.itemList;wx.getSystemInfo({ // 获取系统信息success: sysData => {this.sysData = sysData// 设置画布宽高,this.sysData.windowWidth为屏幕的宽度this.setData({canvasWidth: this.sysData.windowWidth * canvasPre, // 如果觉得不清晰的话,可以把所有组件、宽高放大一倍canvasHeight: this.sysData.windowWidth * canvasPre * hCw,})}})},// 上传图片uploadImg() {let that = this;wx.chooseImage({count: 1,sizeType: ['original', 'compressed'],sourceType: ['album', 'camera'],success (res) {// tempFilePath可以作为img标签的src属性显示图片that.setDropItem({url: res.tempFilePaths[0]});}})},// 设置图片的信息setDropItem(imgData) {let data = {}; // 存储图片信息// 获取图片信息,网络图片需先配置download域名才能生效wx.getImageInfo({src: imgData.url,success: res => {// 初始化数据let maxWidth = 150, maxHeight = 150; // 设置最大宽高if (res.width > maxWidth || res.height > maxHeight) { // 原图宽或高大于最大值就执行if (res.width / res.height > maxWidth / maxHeight) { // 判断比例使用最大值的宽或高作为基数计算data.width = maxWidth;data.height = Math.round(maxWidth * (res.height / res.width));} else {data.height = maxHeight;data.width = Math.round(maxHeight * (res.width / res.height));}   }data.image = imgData.url; // 显示地址data.initImage = imgData.url; // 原始地址data.id = ++itemId; // iddata.top = 0; // top定位data.left = 0; // left定位// 圆心坐标data.x = data.left + data.width / 2;data.y = data.top + data.height / 2;data.scale = 1; // scale缩放data.rotate = 1; // 旋转角度data.active = false; // 选中状态items[items.length] = data; // 每增加一张图片数据增加一条信息this.setData({itemList: items})}})},// 点击图片WraptouchStart: function(e) {// 循环图片数组获取点击的图片信息for (let i = 0; i < items.length; i++) {items[i].active = false;if (e.currentTarget.dataset.id == items[i].id) {index = i;items[index].active = true;}}this.setData({itemList: items})// 获取点击的坐标值items[index].lx = e.touches[0].clientX;items[index].ly = e.touches[0].clientY;},// 拖动图片WraptouchMove(e) {items[index]._lx = e.touches[0].clientX;items[index]._ly = e.touches[0].clientY;items[index].left += items[index]._lx - items[index].lx;items[index].top += items[index]._ly - items[index].ly;items[index].x += items[index]._lx - items[index].lx;items[index].y += items[index]._ly - items[index].ly;items[index].lx = e.touches[0].clientX;items[index].ly = e.touches[0].clientY;this.setData({itemList: items})},// 放开图片WraptouchEnd() {this.synthesis(); // 调用合成图方法},// 点击伸缩图标oTouchStart(e) {//找到点击的那个图片对象,并记录for (let i = 0; i < items.length; i++) {items[i].active = false;if (e.currentTarget.dataset.id == items[i].id) {index = i;items[index].active = true;}}//获取作为移动前角度的坐标items[index].tx = e.touches[0].clientX;items[index].ty = e.touches[0].clientY;//移动前的角度items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty);//获取图片半径items[index].r = this.getDistancs(items[index].x, items[index].y, items[index].left, items[index].top);},oTouchMove: function(e) {//记录移动后的位置items[index]._tx = e.touches[0].clientX;items[index]._ty = e.touches[0].clientY;//移动的点到圆心的距离items[index].disPtoO = this.getDistancs(items[index].x, items[index].y, items[index]._tx, items[index]._ty - 10)items[index].scale = items[index].disPtoO / items[index].r;//移动后位置的角度items[index].angleNext = this.countDeg(items[index].x, items[index].y, items[index]._tx, items[index]._ty)//角度差items[index].new_rotate = items[index].angleNext - items[index].anglePre;//叠加的角度差items[index].rotate += items[index].new_rotate;items[index].angle = items[index].rotate; //赋值//用过移动后的坐标赋值为移动前坐标items[index].tx = e.touches[0].clientX;items[index].ty = e.touches[0].clientY;items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty)//赋值setData渲染this.setData({itemList: items})},// 计算坐标点到圆心的距离getDistancs(cx, cy, pointer_x, pointer_y) {var ox = pointer_x - cx;var oy = pointer_y - cy;return Math.sqrt(ox * ox + oy * oy);},/**参数cx和cy为图片圆心坐标*参数pointer_x和pointer_y为手点击的坐标*返回值为手点击的坐标到圆心的角度*/countDeg: function(cx, cy, pointer_x, pointer_y) {var ox = pointer_x - cx;var oy = pointer_y - cy;var to = Math.abs(ox / oy);var angle = Math.atan(to) / (2 * Math.PI) * 360;if (ox < 0 && oy < 0) //相对在左上角,第四象限,js中坐标系是从左上角开始的,这里的象限是正常坐标系  {angle = -angle;} else if (ox <= 0 && oy >= 0) //左下角,3象限  {angle = -(180 - angle)} else if (ox > 0 && oy < 0) //右上角,1象限  {angle = angle;} else if (ox > 0 && oy > 0) //右下角,2象限  {angle = 180 - angle;}return angle;},deleteItem: function(e) {let newList = [];for (let i = 0; i < items.length; i++) {if (e.currentTarget.dataset.id != items[i].id) {newList.push(items[i])}}if (newList.length > 0) {newList[newList.length - 1].active = true; // 剩下图片组最后一个选中}items = newList;this.setData({itemList: items})},// 打开遮罩层openMask() {this.synthesis();this.setData({showCanvas: true})},synthesis() { // 合成图片maskCanvas.save();maskCanvas.beginPath();// 画背景色(白色)maskCanvas.setFillStyle('#fff');maskCanvas.fillRect(0, 0, this.data.canvasWidth, this.data.canvasHeight);items.forEach((currentValue, index) => {maskCanvas.save();maskCanvas.translate(0, 0);maskCanvas.beginPath();maskCanvas.translate(currentValue.x, currentValue.y); // 圆心坐标maskCanvas.rotate(currentValue.angle * Math.PI / 180);maskCanvas.translate(-(currentValue.width * currentValue.scale / 2), -(currentValue.height * currentValue.scale / 2))maskCanvas.drawImage(currentValue.image, 0, 0, currentValue.width * currentValue.scale, currentValue.height * currentValue.scale);maskCanvas.restore();})// reserve 参数为 false,则在本次调用绘制之前 native 层会先清空画布再继续绘制maskCanvas.draw(false, (e) => {wx.canvasToTempFilePath({canvasId: 'maskCanvas',success: res => {this.setData({canvasTemImg: res.tempFilePath})}}, this);})},// 点击切换显示过滤框toggleFliters() {this.setData({showFliters: !this.data.showFliters,showShapes: false});},// 点击切换显示形状框toggleShapes() {this.setData({showShapes: !this.data.showShapes,showFliters: false});},// 图片特效imgEffects(e) {fliter = e.currentTarget.dataset.fliter || 'init';shape = e.currentTarget.dataset.shape || 'init';let that = this;items.forEach((currentValue, index) => {if (currentValue.active) {maskCanvas.save();maskCanvas.beginPath();util.imgShapes(maskCanvas, 0, 0, currentValue.width, currentValue.width, shape, 0, currentValue); // 图片剪切不同形状maskCanvas.clearRect(0, 0, currentValue.width, currentValue.height);maskCanvas.drawImage(currentValue.initImage, 0, 0, currentValue.width, currentValue.height);maskCanvas.draw(false, function() {wx.canvasGetImageData({ // 获取canvas区域的像素数据canvasId: 'maskCanvas',x: 0,y: 0,width: currentValue.width,height: currentValue.height,success(res) {let imageData = res.data;util.imgFliters(maskCanvas, fliter, res); // 调用图片滤镜函数maskCanvas.clearRect(0, 0, currentValue.width, currentValue.height); // 清除旧的,不然会导致卡顿maskCanvas.restore();wx.canvasPutImageData({ // 将像素数据绘制到canvascanvasId: 'maskCanvas',x: 0,y: 0,width: currentValue.width,height: currentValue.height,data: imageData,success(res) {wx.canvasToTempFilePath({canvasId: 'maskCanvas',width: currentValue.width,height: currentValue.height,destWidth: currentValue.width,destHeight: currentValue.height,success: res => {items[index].image = res.tempFilePaththat.setData({itemList: items})}}, this)}})}});})};})},// 关闭遮罩层disappearCanvas() {this.setData({showCanvas: false})},// 保存图片到系统相册saveImg: function() {wx.saveImageToPhotosAlbum({filePath: this.data.canvasTemImg,success: res => {wx.showToast({title: '保存成功',icon: "success"})},fail: res => {wx.openSetting({success: settingdata => {if (settingdata.authSetting['scope.writePhotosAlbum']) {console.log('获取权限成功,给出再次点击图片保存到相册的提示。')} else {console.log('获取权限失败,给出不给权限就无法正常使用的提示')}},fail: error => {console.log(error)}})wx.showModal({title: '提示',content: '保存失败,请确保相册权限已打开',})}})}
})

查看公众号信息:

微信小程序实现图片拖动、放大、缩小、旋转、滤镜和切图功能相关推荐

  1. 微信小程序,图片双指放大缩小

    不说废话,直接上代码,复制到项目改就完事了 wxml代码 <!--index.wxml--> <view class='wrapper'><view class=&quo ...

  2. 微信小程序禁止图片双击放大

    /* 禁止图片点击缩放 */ image{ pointer-events:none; }

  3. 微信小程序操作图片放大、缩小、旋转、拖拽

    微信小程序操作图片放大.缩小.旋转.拖拽 在开发小程序的时候遇到了上传图片,然后编辑的需求.打算网上找一下相关代码组件的,就不用自己搞这么麻烦.经过一番折腾,还是没有找到如意的,最后自己弄好了,记录一 ...

  4. 微信小程序裁剪图片成圆形

    代码地址如下: http://www.demodashi.com/demo/14453.html 前言 最近在开发小程序,产品经理提了一个需求,要求微信小程序换头像,用户剪裁图片必须是圆形,也在git ...

  5. 微信小程序实现图片是上传、预览功能

    本文实例讲述了微信小程序实现图片上传.删除和预览功能的方法,分享给大家供大家参考,具体如下: 这里主要介绍一下微信小程序的图片上传图片删除和图片预览 1.可以调用相机也可以从本地相册选择 2.本地实现 ...

  6. 微信小程序中base64转换成图片;uni-app小程序base64转图片;微信小程序base64文件转图片;微信小程序base64图片转图片

    将微信小程序的图片转成base64 点击此链接看另一篇 以下是将后端返回的base64转成图片: 方法1:使用微信小程序自带方法 //把base64转换成图片getBase64ImageUrl: (b ...

  7. php微信小程序多图上传,tp5实现微信小程序多图片上传到服务器功能

    最近在做一个教育类的小商城的微信小程序,用到了上传多个图片文件到服务器端,这里做一个讲解,希望对大家有所帮助. 1,小程序端: 在wxml文件中: 删除 点击上传作业 在js文件中: Page({ / ...

  8. uniapp 微信小程序开发 图片上传压缩

    uniapp 微信小程序开发 图片上传压缩 安卓上传图片并压缩 思路 全部代码 安卓上传图片并压缩 由于后端接口对图片的大小有限制,所以在上传图片是需要压缩处理: uni.chooseImage({c ...

  9. 微信小程序image图片预览时不显示(后缀问题!)

    微信小程序image图片预览时不显示(后缀问题!) ".PNG"是不行滴!!! ".png"才可以!!! 刚刚用了两张图片是截下来的,后缀是".PNG ...

最新文章

  1. 全球及中国潜水压力传感器行业运行态势及发展战略研究报告2022-2027年
  2. linux课程_linux系统使用课程更新提示
  3. Unknown column 'XXX' in 'where clause'一例排查
  4. ubuntu下面的robo3t 使用笔记
  5. socket php验证客户端验证,用Socket发送电子邮件(利用需要验证的SMTP服务器)_php基础...
  6. 大数据-----软件开发模型(详细讲解)
  7. 力扣 有序数组的平方
  8. 【PHP】字符串-访问子串
  9. 对称加密、非对称加密、RSA、消息摘要、数字签名、数字证书与 HTTPS 简介
  10. VBA 提取 Office 2016 工具栏图标
  11. ZStack实践汇 | 高效开发测试打造产品化私有云
  12. 手动ghost装服务器系统教程,手动ghost教程图解
  13. 力荐收藏:新QC七大工具(完整版)
  14. matlab 叉乘变点乘,向量点乘和叉乘
  15. Specification查询
  16. 阿里巴巴、腾讯投资Barefoot,助力C轮融资8000万美元
  17. DAX——FILTER
  18. Icpc 沈阳 Bitwise Exclusive-OR Sequence
  19. 在线文件不落地转base64
  20. 时钟表24进制HTML,24进制数字电子钟时计器、译码显示电路 具有自动清零功能

热门文章

  1. C/C++中如何将输出的数字精确到小数点后几位呢?
  2. 爱奇艺 2021秋招在线笔试
  3. 淘宝API 商品详情接口 - 外贸多语翻译商品详情店铺分类开放平台接口
  4. 计算机分隔线教程,计算机CAD画线教程
  5. Hibernate笔记节选
  6. BlackBerry 设备上屏幕和应用程序图标的默认主题和尺寸
  7. COOX基础培训之SCADA(三)
  8. 嵌入式分享合集121
  9. SSH Keylogger密码抓取
  10. CMAIL4.0 server 脚本字段解释