使用 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>
以上。