微信小程序 图片裁剪工具,简单易用
项目需求
在做微信小程序的时候有个图片上传之前裁剪的需求,找过一些github中的项目,都不太理想,主要是没有办法自定义宽高,于是自己研究了一下,做了一个简单的图片裁剪效果
使用方法
项目里面直接放的该功能的文件,将这个文件夹复制到小程序项目中,在app.json中启动该页面即可看到效果
微信小程序技术比较新,所以在网上找了很久都没有找到理想的裁剪插件,正好这两天不是很忙就自己查找资料做了,经过一天的努力终于把它做出来了,不容易啊,现在把它分享给你们(可为你门节省一天开发时间哦):
wxml:
<!--图片展示 --> <view bindtap='upEwm' data-which='1' > <view>第一个图</view> <image style='200rpx;height:200rpx;background-color:red' src='{{headImg}}'></image> </view> <view bindtap='upEwm' data-which='2' > <view>第二个图</view> <image style='200rpx;height:200rpx;background-color:red' src='{{ewmImg}}'></image> </view> <!--裁剪图片浮层--> <view class='fixed-upimg' wx:if="{{imageFixed}}"> <view class="wx-content-info" > <!-- <view class="wx-content-info" wx:if="{{imageSrc}}"> --> <view wx:if="{{isShowImg}}" class="wx-corpper" style="{{cropperInitW}}rpx;height:{{cropperInitH}}rpx;background:#000"> <view bindtap='upLoad' class="wx-corpper-content" style="{{cropperW}}rpx;height:{{cropperH}}rpx;left:{{cropperL}}rpx;top:{{cropperT}}rpx"> <image src="{{imageSrc}}" style="{{cropperW}}rpx;height:{{cropperH}}rpx"></image> <view class="wx-corpper-crop-box" bindtouchstart="contentStartMove" bindtouchmove="contentMoveing" style="{{cutW}}rpx;height:{{cutH}}rpx;left:{{cutL}}rpx;top:{{cutT}}rpx"> <view class="wx-cropper-view-box"> <!-- <view class="wx-cropper-viewer"> <image src="{{imageSrc}}" style="{{cropperW}}rpx;height:{{cropperH}}rpx;left:{{cropperL - cutL}}rpx;top:{{cropperT - cutT}}rpx"></image> </view> --> <view class="wx-cropper-dashed-h"></view> <view class="wx-cropper-dashed-v"></view> <view class="wx-cropper-line-t" data-drag="top" catchtouchstart="dragStart" catchtouchmove="dragMove"></view> <view class="wx-cropper-line-r" data-drag="right" catchtouchstart="dragStart" catchtouchmove="dragMove"></view> <view class="wx-cropper-line-b" data-drag="bottom" catchtouchstart="dragStart" catchtouchmove="dragMove"></view> <view class="wx-cropper-line-l" data-drag="left" catchtouchstart="dragStart" catchtouchmove="dragMove"></view> <view class="wx-cropper-point point-t" data-drag="top" catchtouchstart="dragStart" catchtouchmove="dragMove"></view> <view class="wx-cropper-point point-tr" data-drag="topTight"></view> <view class="wx-cropper-point point-r" data-drag="right" catchtouchstart="dragStart" catchtouchmove="dragMove"></view> <view class="wx-cropper-point point-rb" data-drag="rightBottom" catchtouchstart="dragStart" catchtouchmove="dragMove"></view> <view class="wx-cropper-point point-b" data-drag="bottom" catchtouchstart="dragStart" catchtouchmove="dragMove"></view> <view class="wx-cropper-point point-bl" data-drag="bottomLeft"></view> <view class="wx-cropper-point point-l" data-drag="left" catchtouchstart="dragStart" catchtouchmove="dragMove"></view> <view class="wx-cropper-point point-lt" data-drag="leftTop"></view> </view> </view> </view> <!-- <view class="wx-cropper-drag-box"></view> --> </view> <canvas canvas-id="myCanvas" style="position:absolute;border: 1px solid red; {{imageW}}rpx;height:{{imageH}}rpx;top:-9999px;left:-9999px;"></canvas> <button type="primary" bindtap="getImageInfo" style="position:fixed;bottom:120rpx;90%;left:50%;transform:translate3d(-50%,0,0)"> 确认提交 </button> </view> </view>
js:
// pages/wx-cropper/index.js // 手机的宽度 var windowWRPX = 750 // 拖动时候的 pageX var pageX = 0 // 拖动时候的 pageY var pageY = 0 var pixelRatio = wx.getSystemInfoSync().pixelRatio // 调整大小时候的 pageX var sizeConfPageX = 0 // 调整大小时候的 pageY var sizeConfPageY = 0 var initDragCutW = 0 var initDragCutL = 0 var initDragCutH = 0 var initDragCutT = 0 // 移动时 手势位移与 实际元素位移的比 var dragScaleP = 2 Page({ /** * 页面的初始数据 */ data: { imageNum:'', //上传的图片id headImg:'', //头像上传 ewmImg:'', //二维码上传 imageFixed:false, //裁剪浮层 // imageSrc: 'http://topmdrt-static.oss-cn-shenzhen.aliyuncs.com/images/testimg2.jpeg', imageSrc: '', //要裁剪的图片 returnImage: '', isShowImg: false, // 初始化的宽高 cropperInitW: windowWRPX, cropperInitH: windowWRPX, // 动态的宽高 cropperW: windowWRPX, cropperH: windowWRPX, // 动态的left top值 cropperL: 0, cropperT: 0, // 图片缩放值 scaleP: 0, imageW: 0, imageH: 0, // 裁剪框 宽高 cutW: 400, cutH: 400, cutL: 0, cutT: 0, }, /** * 生命周期函数--监听页面加载 */ onReady: function (options) { }, /** * 生命周期函数--监听页面初次渲染完成 */ onLoad: function () { var _this = this // wx.showLoading({ // title: '图片加载中...', // }) wx.getImageInfo({ src: _this.data.imageSrc, success: function success(res) { var innerAspectRadio = res.width / res.height; console.log(innerAspectRadio) // 根据图片的宽高显示不同的效果 保证图片可以正常显示 if (innerAspectRadio >= 1) { _this.setData({ cropperW: windowWRPX, cropperH: windowWRPX / innerAspectRadio, // 初始化left right cropperL: Math.ceil((windowWRPX - windowWRPX) / 2), cropperT: Math.ceil((windowWRPX - windowWRPX / innerAspectRadio) / 2), // 裁剪框 宽高 // cutW: windowWRPX - 200, // cutH: windowWRPX / innerAspectRadio - 200, cutL: Math.ceil((windowWRPX - windowWRPX + 340) / 2), cutT: Math.ceil((windowWRPX / innerAspectRadio - (windowWRPX / innerAspectRadio - 20)) / 2), // 图片缩放值 scaleP: res.width * pixelRatio / windowWRPX, // 图片原始宽度 rpx imageW: res.width * pixelRatio, imageH: res.height * pixelRatio }) } else { _this.setData({ cropperW: windowWRPX * innerAspectRadio, cropperH: windowWRPX, // 初始化left right cropperL: Math.ceil((windowWRPX - windowWRPX * innerAspectRadio) / 2), cropperT: Math.ceil((windowWRPX - windowWRPX) / 2), // 裁剪框的宽高 // cutW: windowWRPX * innerAspectRadio - 66, // cutH: 400, cutL: Math.ceil((windowWRPX * innerAspectRadio - (windowWRPX * innerAspectRadio - 20)) / 2), cutT: Math.ceil((windowWRPX - 340) / 2), // 图片缩放值 scaleP: res.width * pixelRatio / windowWRPX, // 图片原始宽度 rpx imageW: res.width * pixelRatio, imageH: res.height * pixelRatio }) } _this.setData({ isShowImg: true }) wx.hideLoading() } }) }, // 拖动时候触发的touchStart事件 contentStartMove (e) { pageX = e.touches[0].pageX pageY = e.touches[0].pageY }, // 拖动时候触发的touchMove事件 contentMoveing (e) { var _this = this // _this.data.cutL + (e.touches[0].pageX - pageX) // console.log(e.touches[0].pageX) // console.log(e.touches[0].pageX - pageX) var dragLengthX = (pageX - e.touches[0].pageX) * dragScaleP var dragLengthY = (pageY - e.touches[0].pageY) * dragScaleP var minX = Math.max(_this.data.cutL - (dragLengthX), 0) var minY = Math.max(_this.data.cutT - (dragLengthY), 0) var maxX = _this.data.cropperW - _this.data.cutW var maxY = _this.data.cropperH - _this.data.cutH this.setData({ cutL: Math.min(maxX, minX), cutT: Math.min(maxY, minY), }) console.log(`${maxX} ----- ${minX}`) pageX = e.touches[0].pageX pageY = e.touches[0].pageY }, // 获取图片 getImageInfo () { var _this = this console.log('shengcheng:'+_this.data.imageSrc) wx.showLoading({ title: '图片生成中...', }) // wx.downloadFile({ // url:_this.data.imageSrc, //仅为示例,并非真实的资源 // success: function (res) { // 将图片写入画布 const ctx = wx.createCanvasContext('myCanvas') // ctx.drawImage(res.tempFilePath) ctx.drawImage(_this.data.imageSrc) ctx.draw(true, () => { // 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 var canvasW = (_this.data.cutW / _this.data.cropperW) * (_this.data.imageW / pixelRatio) var canvasH = (_this.data.cutH / _this.data.cropperH) * (_this.data.imageH / pixelRatio) var canvasL = (_this.data.cutL / _this.data.cropperW) * (_this.data.imageW / pixelRatio) var canvasT = (_this.data.cutT / _this.data.cropperH) * (_this.data.imageH / pixelRatio) console.log(`canvasW:${canvasW} --- canvasH: ${canvasH} --- canvasL: ${canvasL} --- canvasT: ${canvasT} -------- _this.data.imageW: ${_this.data.imageW} ------- _this.data.imageH: ${_this.data.imageH} ---- pixelRatio ${pixelRatio}`) wx.canvasToTempFilePath({ x: canvasL, y: canvasT, canvasW, height: canvasH, destWidth: canvasW, destHeight: canvasH, canvasId: 'myCanvas', success: function (res) { wx.hideLoading() // 成功获得地址的地方 console.log('end:'+res.tempFilePath) // 判断时上传头像还是二维码 _this.setData({ imageFixed: false, }) if (_this.data.imageNum == '1') { _this.setData({ headImg: res.tempFilePath }) } else if (_this.data.imageNum == '2') { _this.setData({ ewmImg: res.tempFilePath }) } } }) }) // } // }) }, // 设置大小的时候触发的touchStart事件 dragStart(e) { var _this = this sizeConfPageX = e.touches[0].pageX sizeConfPageY = e.touches[0].pageY initDragCutW = _this.data.cutW initDragCutL = _this.data.cutL initDragCutT = _this.data.cutT initDragCutH = _this.data.cutH }, // 设置大小的时候触发的touchMove事件 dragMove (e) { var _this = this var dragType = e.target.dataset.drag switch (dragType) { case 'right': var dragLength = (sizeConfPageX - e.touches[0].pageX) * dragScaleP if (initDragCutW >= dragLength) { // 如果 移动小于0 说明是在往下啦 放大裁剪的高度 这样一来 图片的高度 最大 等于 图片的top值加 当前图片的高度 否则就说明超出界限 if (dragLength < 0 && _this.data.cropperW > initDragCutL + _this.data.cutW) { this.setData({ cutW: initDragCutW - dragLength }) } // 如果是移动 大于0 说明在缩小 只需要缩小的距离小于原本裁剪的高度就ok if (dragLength > 0) { this.setData({ cutW: initDragCutW - dragLength }) } else { return } } else { return } break; case 'left': var dragLength = (dragLength = sizeConfPageX - e.touches[0].pageX) * dragScaleP console.log(dragLength) if (initDragCutW >= dragLength && initDragCutL > dragLength) { if (dragLength < 0 && Math.abs(dragLength) >= initDragCutW) return this.setData({ cutL: initDragCutL - dragLength, cutW: initDragCutW + dragLength }) } else { return; } break; case 'top': var dragLength = (sizeConfPageY - e.touches[0].pageY) * dragScaleP if (initDragCutH >= dragLength && initDragCutT > dragLength) { if (dragLength < 0 && Math.abs(dragLength) >= initDragCutH) return this.setData({ cutT: initDragCutT - dragLength, cutH: initDragCutH + dragLength }) } else { return; } break; case 'bottom': var dragLength = (sizeConfPageY - e.touches[0].pageY) * dragScaleP // console.log(_this.data.cropperH > _this.data.cutT + _this.data.cutH) console.log(dragLength) console.log(initDragCutH >= dragLength) console.log(_this.data.cropperH > initDragCutT + _this.data.cutH) // 必须是 dragLength 向上缩小的时候必须小于原本的高度 if (initDragCutH >= dragLength) { // 如果 移动小于0 说明是在往下啦 放大裁剪的高度 这样一来 图片的高度 最大 等于 图片的top值加 当前图片的高度 否则就说明超出界限 if (dragLength < 0 && _this.data.cropperH > initDragCutT + _this.data.cutH) { this.setData({ cutH: initDragCutH - dragLength }) } // 如果是移动 大于0 说明在缩小 只需要缩小的距离小于原本裁剪的高度就ok if (dragLength > 0) { this.setData({ cutH: initDragCutH - dragLength }) } else{ return } } else { return } break; case 'rightBottom': var dragLengthX = (sizeConfPageX - e.touches[0].pageX) * dragScaleP var dragLengthY = (sizeConfPageY - e.touches[0].pageY) * dragScaleP if (initDragCutH >= dragLengthY && initDragCutW >= dragLengthX) { // bottom 方向的变化 if ((dragLengthY < 0 && _this.data.cropperH > initDragCutT + _this.data.cutH) || (dragLengthY > 0)) { this.setData({ cutH: initDragCutH - dragLengthY }) } // right 方向的变化 if ((dragLengthX < 0 && _this.data.cropperW > initDragCutL + _this.data.cutW) || (dragLengthX > 0)) { this.setData({ cutW: initDragCutW - dragLengthX }) } else { return } } else { return } break; default: break; } }, // 图片上传 upLoad: function () { }, upEwm: function(e){ var _this = this wx.chooseImage({ count: 1, // 默认9 sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有 sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有 success: function (res) { // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片 var tempFilePaths = res.tempFilePaths; // var mode = parseFloat(e.currentTarget.dataset.current); console.log('shangchuan:' + tempFilePaths) console.log(e.currentTarget.dataset.which); _this.setData({ imageFixed: true, imageSrc: tempFilePaths.join(), imageNum: e.currentTarget.dataset.which }) // start wx.getImageInfo({ src: _this.data.imageSrc, success: function success(res) { var innerAspectRadio = res.width / res.height; console.log('bili'+innerAspectRadio) // 根据图片的宽高显示不同的效果 保证图片可以正常显示 if (innerAspectRadio == '1') { console.log('zhengfangxingtu') _this.setData({ imageFixed: false, }) // 判断时上传头像还是二维码 if (_this.data.imageNum == '1') { _this.setData({ headImg: tempFilePaths.join() }) } else if (_this.data.imageNum == '2') { _this.setData({ ewmImg: tempFilePaths.join() }) } } else if (innerAspectRadio > 1) { _this.setData({ cropperW: windowWRPX, cropperH: windowWRPX / innerAspectRadio, // 初始化left right cropperL: Math.ceil((windowWRPX - windowWRPX) / 2), cropperT: Math.ceil((windowWRPX - windowWRPX / innerAspectRadio) / 2), // 裁剪框 宽高 // cutW: windowWRPX - 200, // cutH: windowWRPX / innerAspectRadio - 200, cutL: Math.ceil((windowWRPX - windowWRPX + 340) / 2), cutT: Math.ceil((windowWRPX / innerAspectRadio - (windowWRPX / innerAspectRadio - 20)) / 2), // 图片缩放值 scaleP: res.width * pixelRatio / windowWRPX, // 图片原始宽度 rpx imageW: res.width * pixelRatio, imageH: res.height * pixelRatio }) } else { _this.setData({ cropperW: windowWRPX * innerAspectRadio, cropperH: windowWRPX, // 初始化left right cropperL: Math.ceil((windowWRPX - windowWRPX * innerAspectRadio) / 2), cropperT: Math.ceil((windowWRPX - windowWRPX) / 2), // 裁剪框的宽高 // cutW: windowWRPX * innerAspectRadio - 66, // cutH: 400, cutL: Math.ceil((windowWRPX * innerAspectRadio - (windowWRPX * innerAspectRadio - 20)) / 2), cutT: Math.ceil((windowWRPX - 340) / 2), // 图片缩放值 scaleP: res.width * pixelRatio / windowWRPX, // 图片原始宽度 rpx imageW: res.width * pixelRatio, imageH: res.height * pixelRatio }) } _this.setData({ isShowImg: true }) wx.hideLoading() } }) // end } }) }, /** * 生命周期函数--监听页面显示 */ onShow: function () { }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
wxss:
/* pages/wx-cropper/index.wxss */ .fixed-upimg{ position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: #fff; } .wx-content-info{ position: fixed; top: 130rpx; left: 0; right: 0; bottom: 0; } .wx-corpper{ position: relative; overflow: hidden; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; box-sizing: border-box; } .wx-corpper-content{ position: absolute; top: 0; right: 0; bottom: 0; left: 0; } .wx-corpper-content image { display: block; 100%; min- 0 !important; max- none !important; height: 100%; min-height: 0 !important; max-height: none !important; image-orientation: 0deg !important; margin: 0 auto; } /* 移动图片效果 */ .wx-cropper-drag-box{ position: absolute; top: 0; right: 0; bottom: 0; left: 0; cursor: move; background: rgba(0,0,0,0.6); z-index: 1; } /* 内部的信息 */ .wx-corpper-crop-box{ position: absolute; 500rpx; height: 500rpx; background: rgba(255,255,255,0.3); z-index: 2; } .wx-corpper-crop-box .wx-cropper-view-box { position: relative; display: block; 100%; height: 100%; overflow: visible; outline: 1px solid #69f; outline-color: rgba(102, 153, 255, .75) } /* 横向虚线 */ .wx-cropper-dashed-h{ position: absolute; top: 33.33333333%; left: 0; 100%; height: 33.33333333%; border-top: 1px dashed rgba(255,255,255,0.5); border-bottom: 1px dashed rgba(255,255,255,0.5); } /* 纵向虚线 */ .wx-cropper-dashed-v{ position: absolute; left: 33.33333333%; top: 0; 33.33333333%; height: 100%; border-left: 1px dashed rgba(255,255,255,0.5); border-right: 1px dashed rgba(255,255,255,0.5); } /* 四个方向的线 为了之后的拖动事件*/ .wx-cropper-line-t{ position: absolute; display: block; 100%; background-color: #69f; top: 0; left: 0; height: 1px; opacity: 0.1; cursor: n-resize; } .wx-cropper-line-t::before{ content: ''; position: absolute; top: 50%; right: 0rpx; 100%; -webkit-transform: translate3d(0,-50%,0); transform: translate3d(0,-50%,0); bottom: 0; height: 41rpx; background: transparent; z-index: 11; } .wx-cropper-line-r{ position: absolute; display: block; background-color: #69f; top: 0; right: 0px; 1px; opacity: 0.1; height: 100%; cursor: e-resize; } .wx-cropper-line-r::before{ content: ''; position: absolute; top: 0; left: 50%; 41rpx; -webkit-transform: translate3d(-50%,0,0); transform: translate3d(-50%,0,0); bottom: 0; height: 100%; background: transparent; z-index: 11; } .wx-cropper-line-b{ position: absolute; display: block; 100%; background-color: #69f; bottom: 0; left: 0; height: 1px; opacity: 0.1; cursor: s-resize; } .wx-cropper-line-b::before{ content: ''; position: absolute; top: 50%; right: 0rpx; 100%; -webkit-transform: translate3d(0,-50%,0); transform: translate3d(0,-50%,0); bottom: 0; height: 41rpx; background: transparent; z-index: 11; } .wx-cropper-line-l{ position: absolute; display: block; background-color: #69f; top: 0; left: 0; 1px; opacity: 0.1; height: 100%; cursor: w-resize; } .wx-cropper-line-l::before{ content: ''; position: absolute; top: 0; left: 50%; 41rpx; -webkit-transform: translate3d(-50%,0,0); transform: translate3d(-50%,0,0); bottom: 0; height: 100%; background: transparent; z-index: 11; } .wx-cropper-point{ 5px; height: 5px; background-color: #69f; opacity: .75; position: absolute; z-index: 3; } .point-t{ top: -3px; left: 50%; margin-left: -3px; cursor: n-resize; } .point-tr{ top: -3px; left: 100%; margin-left: -3px; cursor: n-resize; } .point-r{ top: 50%; left:100%; margin-left: -3px; margin-top: -3px; cursor: n-resize; } .point-rb{ left: 100%; top: 100%; -webkit-transform: translate3d(-50%,-50%,0); transform: translate3d(-50%,-50%,0); cursor: n-resize; 24rpx; height: 24rpx; background-color: #69f; position: absolute; z-index: 1112; opacity: 1; } .point-b{ left:50%; top: 100%; margin-left: -3px; margin-top: -3px; cursor: n-resize; } .point-bl{ left:0%; top: 100%; margin-left: -3px; margin-top: -3px; cursor: n-resize; } .point-l{ left:0%; top: 50%; margin-left: -3px; margin-top: -3px; cursor: n-resize; } .point-lt{ left:0%; top: 0%; margin-left: -3px; margin-top: -3px; cursor: n-resize; } /* 裁剪框预览内容 */ .wx-cropper-viewer{ position: relative; 100%; height: 100%; overflow: hidden; } .wx-cropper-viewer image{ position: absolute; z-index: 2; }
效果图: