- 使用 movable-area 和 movable-view 实现小程序图片裁剪功能
需要做一个页面以实现图片按照指定比例缩放和裁切的功能。然后发现微信小程序刚好自带 movable-area/movable-view 组件,可以实现缩放和平移等功能。 以受控方式使用 movable-view 组件是真的坑。但终于成功了… 先放一下图片裁剪界面截图。实现了拖动和缩放裁剪图片,以及图片被拖动到裁剪边界之外时自动吸附至边界的功能。图片非本人 _(:3… 被裁剪部分外围的白色边框和半透明黑色遮罩层是使用 movable-area 的 :before 伪元素的 box-shadow 属性实现的。 点 Done 按钮后返回前一页,并且在控制台输出 { x, y, w, h } 形式的图片裁剪信息。进入图片裁剪页面时,通过 URL 参数传入图片地址和裁剪比例。 [图片] 指向代码片段的链接: https://developers.weixin.qq.com/s/0Vk2Zsmr7ggJ JS: Page({ data: { image: "", ratio: 1, imageRatio: 1, x: 0, y: 0, scale: 1, ready: false, animating: false }, onLoad: function (queryParams) { this.imageWidth = 0; this.imageHeight = 0; this.currentChange = null; this.currentScale = null; this.touchLock = false; this.isDone = false; this.initLock = true; this.image = queryParams.image; this.ratio = +queryParams.ratio; }, onReady: function () { var that = this, image = this.image, ratio = this.ratio; wx.getImageInfo({ src: image, success: function (obj) { that.imageWidth = obj.width; that.imageHeight = obj.height; var imageRatio = obj.width / obj.height; that.setData({ image: that.image, ratio: ratio, imageRatio: imageRatio }, function () { var query = wx.createSelectorQuery(); query.select("#crop").boundingClientRect(); query.select("#move").boundingClientRect(); query.exec(function (arr) { var bcr = arr[0]; that.setData({ x: imageRatio > ratio ? bcr.width * (.5 - imageRatio / ratio) : -bcr.width * .5, y: imageRatio > ratio ? -bcr.height * .5 : bcr.height * (.5 - ratio / imageRatio), scale: .5 }); setTimeout(function () { that.initLock = false; that.setData({ ready: true }); }, 20); }); that.query = query; }); } }); }, done: function () { var that = this; this.query.exec(function (bcrs) { var inner = bcrs[0], outer = bcrs[1], leftRatio = (inner.left - outer.left) / outer.width, topRatio = (inner.top - outer.top) / outer.height, scaleX = inner.width / outer.width, scaleY = inner.height / outer.height; if (leftRatio < 0) { leftRatio = 0; } else if (leftRatio + scaleX > 1) { leftRatio = 1 - scaleX; } if (topRatio < 0) { topRatio = 0; } else if (topRatio + scaleY > 1) { topRatio = 1 - scaleY; } var x = Math.round(that.imageWidth * leftRatio), y = Math.round(that.imageHeight * topRatio), w = that.imageWidth * scaleX, h = Math.round(w / that.data.ratio); that.isDone = true; var ec = that.getOpenerEventChannel(); ec.emit('crop', { x: x, y: y, w: Math.round(w), h: h }); wx.navigateBack(); }); }, detached: function () { var ec = this.getOpenerEventChannel(); if (!this.isDone) { ec.emit('cropcancel', null); } }, handleChange: function (e) { if (this.initLock) { return; } if ('x' in e.detail) { this.currentChange = e.detail; } if ('scale' in e.detail) { this.currentScale = e.detail.scale; } }, processChange: function () { var that = this; this.query.exec(function (bcrs) { var inner = bcrs[0], outer = bcrs[1], deltax = 0, deltay = 0; if (inner.left < outer.left) { deltax = inner.left - outer.left; } else if (inner.right > outer.right) { deltax = inner.right - outer.right; } if (inner.top < outer.top) { deltay = inner.top - outer.top; } else if (inner.bottom > outer.bottom) { deltay = inner.bottom - outer.bottom; } if ((deltax || deltay) && that.currentChange) { that.setData({ x: that.currentChange.x + deltax + .01 * Math.random() - .005, y: that.currentChange.y + deltay + .01 * Math.random() - .005, scale: that.currentScale || that.data.scale, animating: deltax * deltax + deltay * deltay > 100 }); setTimeout(() => { that.setData({ animating: false }); }, 100); } that.currentChange = null; }); } }); WXML: <movable-area class="crop" id="crop" style="height:{{80/ratio}}vw"> <movable-view class="crop__view" direction="all" scale id="move" x="{{x}}" y="{{y}}" animation="{{animating || scale>.99}}" scale-max="1" scale-value="{{scale}}" damping="160" out-of-bounds bindscale="handleChange" bindchange="handleChange" bindtouchend="processChange" bindtouchcancel="processChange" style="{{imageRatio>ratio?'width:'+imageRatio*200/ratio+'%;height:200%':'width:200%;height:'+ratio*200/imageRatio+'%'}}" > <image src="{{image}}" class="crop__image" /> </movable-view> </movable-area> <button class="done" bindtap="done">Done</button> <view class="loading-cover {{ready ? 'loading-cover--hidden' : ''}}">Loading</view> 以上。
2020-04-08 - movable-view 设置scale后,添加新的movable-view后,上一个元素会自动偏移
我用了一个数组存放多个movable-view ,我是先开启缩放,再拖拽几下,再添加新的数据到数组后,movable-area这个组件会重新运行,然后上一个元素就自动偏移了。
2023-04-28