微信小程序实现图片拖动、放大、缩小、旋转、滤镜和切图功能(微信小程序图片拖动排序)

示例简介

本文介绍使用微信小程序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, // 当前点击图片的index items = [], // 图片数组信息 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; // id data.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({ // 将像素数据绘制到canvas canvasId: '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.tempFilePath that.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: '保存失败,请确保相册权限已打开', }) } }) }})

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

(0)
上一篇 2023年4月5日 上午9:16
下一篇 2023年4月5日 上午9:32

相关推荐