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


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 相关文章推荐
ajax更新数据后,jquery、jq失效问题
Mar 16 Javascript
js禁止页面使用右键(简单示例代码)
Nov 13 Javascript
jquery中ajax函数执行顺序问题之如何设置同步
Feb 28 Javascript
javascript在当前窗口关闭前检测窗口是否关闭
Sep 29 Javascript
javascript中checkbox使用方法简单实例演示
Nov 17 Javascript
AngularJS实现动态编译添加到dom中的方法
Nov 04 Javascript
详解.vue文件中监听input输入事件(oninput)
Sep 19 Javascript
bootstrap table方法之expandRow-collapseRow展开或关闭当前行数据
Aug 09 Javascript
浅谈Vue2.0父子组件间事件派发机制
Jan 08 Javascript
JavaScript设计模式之装饰者模式实例详解
Jan 17 Javascript
vue-router 中 meta的用法详解
Nov 01 Javascript
利用node.js开发cli的完整步骤
Dec 29 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字符串的连接的简单实例
2013/12/30 PHP
phpmailer在服务器上不能正常发送邮件的解决办法
2014/07/08 PHP
PHP实现微信小程序人脸识别刷脸登录功能
2018/05/24 PHP
PHP写API输出的时用echo的原因详解
2019/04/28 PHP
jQuery Tools tooltip使用说明
2012/07/14 Javascript
使用js对select动态添加和删除OPTION示例代码
2013/08/12 Javascript
jQuery中hover方法和toggle方法使用指南
2015/02/27 Javascript
JavaScript实现俄罗斯方块游戏过程分析及源码分享
2015/03/23 Javascript
JavaScript修改作用域外变量的方法
2016/03/25 Javascript
基于chosen插件实现人员选择树搜索自动筛选功能
2016/09/24 Javascript
浅谈js对象属性 通过点(.) 和方括号([]) 的不同之处
2016/10/29 Javascript
NPM 安装cordova时警告:npm WARN deprecated minimatch@2.0.10: Please update to minimatch 3.0.2 or higher to
2016/12/20 Javascript
Bootstrap表单简单实现代码
2017/03/06 Javascript
JavaScript模块化之使用requireJS按需加载
2017/04/12 Javascript
JS触摸事件、手势事件详解
2017/05/04 Javascript
vue实现表格增删改查效果的实例代码
2017/07/18 Javascript
基于BootStrap的文本编辑器组件Summernote
2017/10/27 Javascript
React中如何引入Angular组件详解
2018/08/09 Javascript
微信小程序如何调用图片接口API并居中显示
2019/06/29 Javascript
Node.js学习之内置模块fs用法示例
2020/01/22 Javascript
mapboxgl区划标签避让不遮盖实现的代码详解
2020/07/01 Javascript
[46:00]Ti4 冒泡赛第二轮LGD vs C9 2
2014/07/14 DOTA
[44:30]完美世界DOTA2联赛PWL S2 GXR vs Magma 第一场 11.25
2020/11/26 DOTA
python使用KNN算法手写体识别
2018/02/01 Python
Python 点击指定位置验证码破解的实现代码
2019/09/11 Python
浅谈图像处理中掩膜(mask)的意义
2020/02/19 Python
python numpy--数组的组合和分割实例
2020/02/24 Python
印度电子产品购物网站:Vijay Sales
2021/02/16 全球购物
土木工程建筑专业毕业生求职信
2013/10/21 职场文书
工厂实习感言
2014/01/14 职场文书
《他得的红圈圈最多》教学反思
2014/04/24 职场文书
2014入党积极分子批评与自我批评思想汇报
2014/09/20 职场文书
入党积极分子群众意见
2015/06/01 职场文书
解决Golang time.Parse和time.Format的时区问题
2021/04/29 Golang
Python 游戏大作炫酷机甲闯关游戏爆肝数千行代码实现案例进阶
2021/10/16 Python
Android开发手册Chip监听及ChipGroup监听
2022/06/10 Java/Android