微信小程序内拖动图片实现移动、放大、旋转

    最近接到一个任务,在微信小程序内拖动图片组件实现移动、放大、旋转,并记录这些图片的移动位置,放大比例,旋转角度,在一个画布上生成一张图片,最后保存到手机相册。

    我的具体实现思路是这样的:(文章底部留有实例git地址)

    一共三个功能,可以先把功能分为图片 拖动 和图片 旋转缩放 , 把图片的缩放和旋转做在了一起。

    1.图片移动:可移动的图片肯定是要动态生成的,所以不能写死,应该是个数组,具备很多的属性。

    例如:(并不是我项目的真实数据)

itemList: [{
            id: 1,
            image: '1.png',//图片地址
            top: 100,//初始图片的位置 
            left: 100,
            x: 155, //初始圆心位置,可再downImg之后又宽高和初始的图片位置得出
            y: 155,
            scale: 1,//缩放比例  1为不缩放
            angle: 0,//旋转角度
            active: false //判定点击状态
        }, {
            id: 2,
            image: '2.png',
            top: 50,
            left: 50,
            x: 155,
            y: 155,
            scale: 1,
            angle: 0,
            active: false
        }]

 

 

 

事件绑定图片上或者图片的父级,绑定bindtouchstart  bindtouchmove事件。再bindtouchstart事件里,获取手指点击的某一个图片的点击坐标,并记录在这个图片对象的属性里面,在bindtouchmove事件里,移动的时候记录移动后的坐标,并算出俩次滑动的距离差值,追加给图片对象的left、top、x、y上,最后把本次滑动的坐标赋值给bindtouchmove事件里拿到的坐标,作为老坐标。这样就可以实现图片的滑动。

 

 

 

注:代码里的 items  只是我定义的一个全局变量,是一个空数组,在onLoad函数里 items = this.data.itemLits; 

这样就不会频繁的去setData,我只需要处理items,处理完之后,再this.setData({itemLits: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;  //开启点击属性
            }
        }
       
        items[index].lx = e.touches[0].clientX;  // 记录点击时的坐标值
        items[index].ly = e.touches[0].clientY;
        this.setData({   //赋值 
            itemList: items
        })
    }
    , WraptouchMove: function (e) {
        //移动时的坐标值也写图片的属性里
        items[index]._lx = e.touches[0].clientX;
        items[index]._ly = e.touches[0].clientY;
        
        //追加改动值
        items[index].left  += items[index]._lx - items[index].lx;  // x方向
        items[index].top += items[index]._ly - items[index].ly;    // y方向
        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
        })
    }

2.图片的旋转和缩放,因为图片上已经有了touch事件,所以解决办法采用常规的在图片的一角添加一个控件解决这个问题,控件大致如图:

左边控件是删除按钮,右边控件则是手指按着旋转切缩放图片的控件,绑定bindtouchstart  bindtouchmove事件。

index也是设置的全局变量。
// 触摸开始事件  items是this.data.itemList的全局变量,便于赋值  所有的值都应给到对应的对象里
    touchStart: function (e) {
       //找到点击的那个图片对象,并记录
        for (let i = 0; i < items.length; i++) {
            items[i].active = false;

            if (e.currentTarget.dataset.id == items[i].id) {
                console.log('e.currentTarget.dataset.id', e.currentTarget.dataset.id)
                index = i;
                console.log(items[index])
                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)
    },
    // 触摸移动事件  
    touchMove: function (e) {
        //记录移动后的位置
        items[index]._tx = e.touches[0].clientX;
        items[index]._ty = e.touches[0].clientY;
        //移动的点到圆心的距离  * 因为圆心的坐标是相对与父元素定位的 ,所有要减去父元素的OffsetLeft和OffsetTop来计算移动的点到圆心的距离
        items[index].disPtoO = this.getDistancs(items[index].x, items[index].y, items[index]._tx - this.sysData.windowWidth * 0.125, items[index]._ty - 10)

        items[index].scale = items[index].disPtoO / items[index].r; //手指滑动的点到圆心的距离与半径的比值作为图片的放大比例
        items[index].oScale = 1 / items[index].scale;//图片放大响应的右下角按钮同比缩小

        //移动后位置的角度
        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
        })
    }

 

 

 

这样一来就解决了微信小程序内拖动图片实现移动、放大、旋转的问题,操作也比较顺滑,也耗费我近四天的时间才把我的小程序上线,代码有点混乱,如果各位大佬有什么意见可以给我留言,我的小程序名字是:水逆转运符文,以后会持续改进。

2018/5/7补充一条生成图片时,组件的属性:

 

 

点击配件时的事件(因为再我测试在canvas中,图片不能是网络路径,所以需要下载): 【18/6/22】

tpDownload: function(data, isDownload) { //data为组件的参数,isDownload判断是否为https网络图片来判断是否需要下载
        if (yy < 0) { //改变生成图片时的位置
            speed = -speed
        }
        if (yy > 300) {
            speed = -speed
        }
        yy += speed;
        let _this = this;
        let newTpdata = {};
        newTpdata.id = data.id;
        newTpdata.itemid = data.itemid;
        newTpdata.top = 100 + yy;
        newTpdata.left = 100;
        newTpdata.width = _this.sysData.windowWidth / 4;
        newTpdata.scale = 1;
        newTpdata.angle = 0;
        newTpdata.rotate = 0;
        newTpdata.active = true;
        for (let i = 0; i < items.length; i++) {
            items[i].active = false;
        }
        if (isDownload) {
            wx.downloadFile({
                url: data.image,
                success: res => {
                    newTpdata.image = res.tempFilePath;
                    items.push(newTpdata);
                    _this.setData({
                        itemList: items
                    })
                    wx.hideLoading();
                }
            })
        } else {
            newTpdata.image = data.image;
            items.push(newTpdata);
            _this.setData({
                itemList: items
            })
            wx.hideLoading();
        }
    }

我的项目中生成canvas用到的代码 (绘图是通过保存按钮触发)

save: function() {
        this.setData({
            showCanvas: true,
            canvasHeight: this.sysData.windowHeight * 0.85
        })
        let obj = this.data.item;
        /*
        canvasWidth值为canvas宽度;
        this.data.canvasPre是占屏幕宽度的百分比(80)
        */
        let canvasWidth = this.sysData.windowWidth * this.data.canvasPre / 100; //
       /*
        num为canvas内背景图占canvas的百分比,若全背景num =1
        this.sysData.windowWidth * 0.75为可移动区的宽度
        prop值为canvas内背景的宽度与可移动区域的宽度的比,如一致,则prop =1;
       */
        let prop = (canvasWidth * num) / (this.sysData.windowWidth * 0.75);
        maskCanvas.save();
        maskCanvas.beginPath();
        //一张白图
        maskCanvas.setFillStyle('#fff');
        maskCanvas.fillRect(0, 0, this.sysData.windowWidth, this.data.canvasHeight)
        maskCanvas.closePath();
        maskCanvas.stroke();
        //图头像
        let image = {
            w: canvasWidth * num * 0.287,
            h: canvasWidth * num * 0.287,
            r: canvasWidth * num * 0.287 / 2
        };
        //画背景 hCw 为 1.7781 背景图的高宽比
        maskCanvas.drawImage(obj.bgImg, canvasWidth * (1 - num) / 2, 10, canvasWidth * num, canvasWidth * num * hCw)
        //画底图
        maskCanvas.drawImage('../../images/xcx.png', canvasWidth * (1 - num) / 2, canvasWidth * num * hCw + 15, canvasWidth * num, this.data.canvasHeight * 0.15)
        //画原
        maskCanvas.save();
        maskCanvas.beginPath();
        maskCanvas.arc(canvasWidth / 2, canvasWidth * num * hCw * obj.userTop / 100 + 10 + image.w / 2, image.r, 0, Math.PI * 2, false);
        // maskCanvas.stroke()
        maskCanvas.clip(); //截取
        //画头像
        maskCanvas.drawImage(obj.avatarUrl, (canvasWidth - image.w) / 2, canvasWidth * num * hCw * obj.userTop / 100 + 10, image.w, image.h)
        maskCanvas.closePath();
        maskCanvas.restore();
        //绘制文字
        maskCanvas.save();
        maskCanvas.beginPath();
        let fontSize = this.sysData.screenWidth / 375 * 15;
        let textColor = obj.color || '#000';
        maskCanvas.setFontSize(parseInt(fontSize) * prop)
        maskCanvas.setFillStyle(textColor)
        maskCanvas.setTextAlign('center')
        maskCanvas.fillText(obj.nickName, canvasWidth / 2, obj.titleTop / 100 * canvasWidth * num * hCw + 10 * 0.9 * prop  + fontSize * prop);
        maskCanvas.closePath();
        maskCanvas.stroke();
        /** 
         * x
         * y
         * scale
         * prop
         * width
         * height
         * 
         */
        //画组件
        items.forEach((currentValue,index)=>{
            maskCanvas.save();
            maskCanvas.translate(canvasWidth * (1 - num) / 2, 10);
            maskCanvas.beginPath();
            maskCanvas.translate(currentValue.x * prop, currentValue.y * prop); //圆心坐标
            maskCanvas.rotate(currentValue.angle * Math.PI / 180); // 旋转值
            maskCanvas.translate(-(currentValue.width * currentValue.scale * prop / 2), -(currentValue.height * currentValue.scale * prop / 2))
            maskCanvas.drawImage(currentValue.image, 0, 0, currentValue.width * currentValue.scale * prop, currentValue.height * currentValue.scale * prop);
            maskCanvas.restore();
        })
        maskCanvas.draw(false, (e)=> {
            wx.canvasToTempFilePath({
                canvasId: 'maskCanvas',
                success: res => {
                    this.setData({
                        canvasTemImg: res.tempFilePath
                    })
                }
            }, this)
        })
    }

 


 

希望我的经验能帮到需要的人。

写的不合理的地方还望大家指正 ^_^
 

【18/6/25】我改了改变量的命名,使其更语义化,备注也写了。

qq: 1092374246

有需要小程序支持的,可以联系我~ 

原项目代码因为CDN过期可能跑步起来了,新提供一套简洁的代码,可自行扩展:

记得给个小星星哦~

git地址:https://github.com/peng20017/wx-drop




 

 

  • 32
    点赞
  • 106
    收藏
    觉得还不错? 一键收藏
  • 119
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 119
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值