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


Posted in Javascript onSeptember 04, 2018

屏幕就像是数学上的坐标轴,且在第四象限,以屏幕左上角为圆点,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)
    })
  }

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

Javascript 相关文章推荐
jquery.simple.tree插件 更简单,兼容性更好的无限树插件
Sep 03 Javascript
JS保存和删除cookie操作 判断cookie是否存在
Nov 13 Javascript
javascript实现简单的Map示例介绍
Dec 23 Javascript
jQuery on()方法绑定动态元素的点击事件无响应的解决办法
Jul 07 Javascript
js控住DOM实现发布微博效果
Aug 30 Javascript
微信小程序开发之animation循环动画实现的让云朵飘效果
Jul 14 Javascript
express框架实现基于Websocket建立的简易聊天室
Aug 10 Javascript
javascript实现最长公共子序列实例代码
Feb 05 Javascript
JS实现获取毫秒值及转换成年月日时分秒的方法
Aug 15 Javascript
深入浅析Node.js 事件循环、定时器和process.nextTick()
Oct 22 Javascript
从零开始在NPM上发布一个Vue组件的方法步骤
Dec 20 Javascript
JS实现按比例缩小图片宽高
Aug 24 Javascript
JS中DOM元素的attribute与property属性示例详解
Sep 04 #Javascript
JavaScript使用indexOf()实现数组去重的方法分析
Sep 04 #Javascript
关于vue v-for 循环问题(一行显示四个,每一行的最右边那个计算属性)
Sep 04 #Javascript
原生JS实现获取及修改CSS样式的方法
Sep 04 #Javascript
微信小程序canvas拖拽、截图组件功能
Sep 04 #Javascript
mpvue写一个CPASS小程序的示例
Sep 04 #Javascript
Vue表单及表单绑定方法
Sep 04 #Javascript
You might like
PHP实现的同步推荐操作API接口案例分析
2016/11/30 PHP
PHP tp5中使用原生sql查询代码实例
2020/10/28 PHP
javascript div 弹出可拖动窗口
2009/02/26 Javascript
JQery 渐变图片导航效果代码 漂亮
2010/01/01 Javascript
JS中getYear()和getFullYear()区别分析
2014/07/04 Javascript
JavaScript中对象property的删除方法介绍
2014/12/30 Javascript
原生js制作简单的数字键盘
2015/04/24 Javascript
分享纯手写漂亮的表单验证
2015/11/19 Javascript
jQuery form 表单验证插件(fieldValue)校验表单
2016/01/24 Javascript
不能不知道的10个angularjs英文学习网站
2016/03/23 Javascript
浅析BootStrap中Modal(模态框)使用心得
2016/12/24 Javascript
浅析jsopn跨域请求原理及cors(跨域资源共享)的完美解决方法
2017/02/06 Javascript
Node.js中的http请求客户端示例(request client)
2017/05/04 Javascript
深入理解Angular中的依赖注入
2017/06/26 Javascript
vue页面离开后执行函数的实例
2018/03/13 Javascript
实例讲解JavaScript截取字符串
2018/11/30 Javascript
JS实现的tab页切换效果完整示例
2018/12/18 Javascript
解决在Vue中使用axios POST请求变成OPTIONS的问题
2020/08/14 Javascript
[03:46]显微镜下的DOTA2第七期——满血与残血
2014/06/20 DOTA
[03:17]2016完美“圣”典风云人物:冷冷专访
2016/12/08 DOTA
Python的Socket编程过程中实现UDP端口复用的实例分享
2016/03/19 Python
python模拟事件触发机制详解
2018/01/19 Python
详解python字节码
2018/02/07 Python
pandas按若干个列的组合条件筛选数据的方法
2018/04/11 Python
python批量修改图片后缀的方法(png到jpg)
2018/10/25 Python
Python 获取指定文件夹下的目录和文件的实现
2019/08/30 Python
Selenium关闭INFO:CONSOLE提示的解决
2020/12/07 Python
HTML5、Select下拉框右边加图标的实现代码(增进用户体验)
2017/10/16 HTML / CSS
工地安全生产标语
2014/06/06 职场文书
初中班级口号
2014/06/09 职场文书
租车协议书范本2014
2014/11/17 职场文书
2015年数学教师工作总结
2015/05/20 职场文书
2016十一国庆节感言
2015/12/09 职场文书
详解JS WebSocket断开原因和心跳机制
2021/05/07 Javascript
JavaScript函数柯里化
2021/11/07 Javascript
Win11如何查看显卡型号 Win11查看显卡型号的方法
2022/08/14 数码科技