欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-29 17:35:08
屏幕就像是数学上的坐标轴,且在第四象限,以屏幕左上角为圆点,x轴向右为正向左为负,y轴向下为正向上为负(这点和数学上相反的)以圆点为基点画个距离圆点上下50宽高100的矩形...

屏幕就像是数学上的坐标轴,且在第四象限,以屏幕左上角为圆点,x轴向右为正向左为负,y轴向下为正向上为负(这点和数学上相反的)以圆点为基点画个距离圆点上下50宽高100的矩形来演示canvas基本用法

微信小程序这里提供了两个api

wx.createcontext() 创建并返回绘图上下文context对象

  • getactions 获取当前context上存储的绘图动作,对应wx.drawcanvas(object)中的actions
  • clearactions 清空当前的存储绘图动作

wx.drawcanvas(object) 绘制

  • canvasid 画布标识,传入的cavas-id,这里的标识可以为number,也可以是string
  • actions 绘图动作数组,由wx.createcontext创建的context,调用getactions方法导出绘图动作数组。

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

我的具体实现思路是这样的:

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

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
    })
  }

页面上是这样写的:

<!-- *************操作区域************* -->
    <block wx:for="{{itemlist}}" wx:key="{{item.id}}">
      <!-- 圆心坐标 <text style='position:absolute;top:{{item.y}}px;left:{{item.x}}px;width:2px;height:2px;background-color:yellow;z-index:500'></text> -->
      <view class='touchwrap' style='transform: scale({{item.scale}});top:{{item.top}}px;left:{{item.left}}px; '>
        <view class='imgwrap {{item.active? "touchactive":""}}' style="transform: rotate({{item.angle}}deg);">
          <image src='{{item.image}}' data-id='{{item.id}}' style='width:{{item.width}}px;height:{{item.height}}px;' bindtouchstart='wraptouchstart' bindload='loadimg' hidden='{{!item.isload}} bindtouchmove='wraptouchmove' bindtouchend='wraptouchend'></image>
          <image class='x' src='../../images/x.png' style='transform: scale({{item.oscale}});transform-origin:center;' data-id='{{item.id}}' bindtap='deleteitem'></image>
          <image class='o' src='../../images/o.png' style='transform: scale({{item.oscale}});transform-origin:center;' data-id='{{item.id}}' bindtouchstart='touchstart' bindtouchmove='touchmove' bindtouchend='touchend'></image>
        </view>
      </view>
    </block>
<!-- **************操作区域************ -->

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

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

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

我的失误,忘了附上角度计算函数  countdeg :  

/*
 *参数1和2为图片圆心坐标
 *参数3和4为手点击的坐标
 *返回值为手点击的坐标到圆心的角度
 */
  countdeg: function (cx, cy, pointer_x, pointer_y) {
    var ox = pointer_x - cx;
    var oy = pointer_y - cy;
    var to = math.abs(ox / oy);
    var angle = math.atan(to) / (2 * math.pi) * 360;//鼠标相对于旋转中心的角度
    console.log("ox.oy:", ox, oy)
    if (ox < 0 && oy < 0)//相对在左上角,第四象限,js中坐标系是从左上角开始的,这里的象限是正常坐标系 
    {
      angle = -angle;
    } else if (ox <= 0 && oy >= 0)//左下角,3象限 
    {
      angle = -(180 - angle)
    } else if (ox > 0 && oy < 0)//右上角,1象限 
    {
      angle = angle;
    } else if (ox > 0 && oy > 0)//右下角,2象限 
    {
      angle = 180 - angle;
    }
 
    return angle;
  }

计算触摸点到圆心的距离:

getdistancs(cx, cy, pointer_x, pointer_y) {
    var ox = pointer_x - cx;
    var oy = pointer_y - cy;
    return math.sqrt(
      ox * ox + oy * oy
    );
  }

点击配件时的事件(因为再我测试在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)
    })
  }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。