使用 movable-area 和 movable-view 实现小程序图片裁剪功能
发布于 4 年前 作者 xiulan86 1378 次浏览 来自 分享

需要做一个页面以实现图片按照指定比例缩放和裁切的功能。然后发现微信小程序刚好自带 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"",
        ratio1,
        imageRatio1,
        x0,
        y0,
        scale1,
        readyfalse,
        animatingfalse
    },
    onLoadfunction (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;
    },
    onReadyfunction () {
        var that = this, image = this.image, ratio = this.ratio;
        wx.getImageInfo({
            src: image,
            successfunction (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({
                                readytrue
                            });
                        }, 20);
                    });
                    that.query = query;
                });
            }
        });
    },
    donefunction () {
        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,
                wMath.round(w),
                h: h
            });

            wx.navigateBack();
        });
    },
    detachedfunction () {
        var ec = this.getOpenerEventChannel();
        if (!this.isDone) {
            ec.emit('cropcancel'null);
        }
    },
    handleChangefunction (e{
        if (this.initLock) {
            return;
        }
        if ('x' in e.detail) {
            this.currentChange = e.detail;
        }
        if ('scale' in e.detail) {
            this.currentScale = e.detail.scale;
        }
    },
    processChangefunction () {
        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({
                        animatingfalse
                    });
                }, 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>

以上。

1 回复

[先点赞再看]

回到顶部