小程序中图片的移动、旋转和缩放功能

先把代码放上来

js逻辑代码

图标移动开始事件
  start(e) {
  //记录移动触摸起点位置
    this.setData({
      x: e.touches[0].clientX,
      y: e.touches[0].clientY,
      iconIndex: e.currentTarget.dataset.index
    })
  },
  //图标移动中事件
  move(e) {
    if (flag) {
      flag = false;
      setTimeout(() => {
        flag = true;
      }, 100)
      let arr = Object.assign({}, this.data.images, {})
      //计算移动的距离  
      arr[e.currentTarget.dataset.index].position = {
        left: e.touches[0].clientX - this.data.x,
        top: e.touches[0].clientY - this.data.y
      }
      //这里是因为页面图片是遍历 images数组动态生成的,所以要修改images的值页面才会重新渲染,根据自身情况决定
      this.setData({
        images: arr
      })
    }
  },
  //图标移动结束
  end(e) {
    let arr = Object.assign({}, this.data.images, {})
    this.data.imgUrl[this.data.imgIndex].props[e.currentTarget.dataset.index].left = arr[e.currentTarget.dataset.index].left = arr[e.currentTarget.dataset.index].left + arr[e.currentTarget.dataset.index].position.left;
    this.data.imgUrl[this.data.imgIndex].props[e.currentTarget.dataset.index].top = arr[e.currentTarget.dataset.index].top = arr[e.currentTarget.dataset.index].top + arr[e.currentTarget.dataset.index].position.top;
    arr[e.currentTarget.dataset.index].position = {
      left: 0,
      top: 0
    };
    this.setData({
      images: arr
    })
  },
  //图标缩放,旋转开始
  scaleStart(e) {
    let p = 500/this.data.imgUrl[this.data.imgIndex].width;//这是为了计算图片的原始尺寸,可忽略
    x1 = ((125 + this.data.imgUrl[this.data.imgIndex].left * p + this.data.imgUrl[this.data.imgIndex].areaWidth / 2 * p) * this.data.p) + this.data.images[e.currentTarget.dataset.index].left;
    y1 = ((102 + this.data.imgUrl[this.data.imgIndex].top * p + this.data.imgUrl[this.data.imgIndex].areaHeight / 2 * p) * this.data.p) + this.data.images[e.currentTarget.dataset.index].top;
	//x1、y1是旋转或者缩放时图片的中心点坐标
    let x = e.touches[0].clientX - x1,
      y = e.touches[0].clientY - y1,
      s = 0;
    let cos = Math.abs(x) / Math.pow((Math.pow(x, 2) + Math.pow(y, 2)), 0.5);
    var radina = Math.acos(cos);
    var angle = Math.floor(180 / (Math.PI / radina));
    if (x > 0) {
      if (y > 0) {
        angle = 360 - angle
      }
    } else {
      if (y > 0) {
        angle += 180
      } else {
        angle = 180 - angle
      }
    }
    //上面的代码是计算旋转或者缩放开始时触摸点和中心坐标与水平X轴的角度
    this.setData({
      actionIndex: e.currentTarget.dataset.index,
      s: Math.pow((Math.pow(x, 2) + Math.pow(y, 2)), 0.5),
      deg1: angle
    })
  },
  //缩放,旋转
  scaleMove(e) {
    if (flag) {
      flag = false;
      setTimeout(() => {
        flag = true
      }, 100)
      //这个定时器是为了解决小程序中setData()使用频率太高导致代码阻塞,页面渲染延迟
      let x = e.touches[0].clientX - x1,
        y = e.touches[0].clientY - y1,
        s = 0;
      s = Math.pow((Math.pow(x, 2) + Math.pow(y, 2)), 0.5);
      let cos = Math.abs(x) / s;
      var radina = Math.acos(cos);
      var angle = Math.floor(180 / (Math.PI / radina));
      if (x > 0) {
        if (y > 0) {
          angle = 360 - angle
        }
      } else {
        if (y > 0) {
          angle += 180
        } else {
          angle = 180 - angle
        }
      }
      //计算旋转或者缩放进行中触摸点和中心坐标与水平X轴的角度,从而得到图片的旋转角度
      if (e.currentTarget.dataset.dragize !== 0) {
        obj.scale = s / this.data.s.toFixed(2)
      }
      obj.rotate = parseInt(this.data.deg1 - angle)
      this.setData(obj)
    }
  },
  //图标缩放,旋转结束
  scaleEnd(e) {
    let arr = Object.assign({}, this.data.images, {});
    this.data.imgUrl[this.data.imgIndex].props[e.currentTarget.dataset.index].scale = arr[e.currentTarget.dataset.index].scale = arr[e.currentTarget.dataset.index].scale * this.data.scale;
    this.data.imgUrl[this.data.imgIndex].props[e.currentTarget.dataset.index].rotate = arr[e.currentTarget.dataset.index].rotate = arr[e.currentTarget.dataset.index].rotate + this.data.rotate;
    this.setData({
      images: arr,
      actionIndex: -1,
      rotate: 0,
      scale: 1,
    });
  },

WXML页面代码

<view wx:for="{{images}}" wx:key="{{index}}" class='box {{index==iconIndex?"active":""}}' style='transform:translateX({{(item.left+item.position.left)/p}}rpx) translateY({{(item.top+item.position.top)/p}}rpx) rotateZ({{actionIndex==index?item.rotate+rotate:item.rotate}}deg) scale({{actionIndex==index?item.scale*scale:item.scale}}); left:{{imgUrl[imgIndex].areaWidth*500/imgUrl[imgIndex].width/2-(item.dragize==1?75:25)}}rpx;top:{{imgUrl[imgIndex].areaHeight/imgUrl[imgIndex].width*500/2-(item.dragize===1?75:25)}}rpx'
      data-index='{{index}}'>
      <image class='boxImg {{item.dragize==1?"":"BADGE"}}' catchtouchstart='start' catchtouchend='end' catchtouchmove="move" data-index="{{index}}" src='{{item.url}}?x-oss-process=image/resize,m_lfit,w_100'></image>
      <view class='moveclose' style='transform:scale({{1/(actionIndex==index?item.scale*scale:item.scale)}})' hidden='{{index!=iconIndex}}' catchtap='moveclose' catchtouchmove data-index="{{index}}"></view>
      <view class='movescale' style='transform:scale({{1/(actionIndex==index?item.scale*scale:item.scale)}})' hidden='{{index!=iconIndex}}' data-dragize='{{item.dragize}}' catchtouchmove="scaleMove" catchtouchstart="scaleStart" catchtouchend='scaleEnd' data-index='{{index}}'></view>
    </view>

代码很乱。。毕竟是撸了三四天才搞出来的= =;哈哈哈,希望能有点帮助

最后把用到的数据结构分享一下

    images: [{
		dragize:1,//该图片是否允许缩放
		height:960,//图片高度 初始值
		id:41,
		left:0,//图片的left值   相对于自己定义的初始位置
		rotate:0,//旋转角度
		scale:1,//缩放倍数
		top:0,//图片的top值   相对于自己定义的初始位置
		position:{
			left:0,// 图片在移动过程中的偏移量
			top:0// 图片在移动过程中的偏移量
		}
		url:"https://octopus-master.oss-cn-shenzhen.aliyuncs.com/sys/0fec4b1864b8341bb84d9216b2cc42df.png",
		width:960//图片宽度 初始值
	}],
    iconIndex: -1, //大图中选中的图标下标
    scale: 1, //缩放比例,图片在缩放过程中的倍数  缩放结束时应该用之前的倍数乘以该倍数
    rotate: 0,//图片在旋转过程中的旋转角度 旋转结束时应该用之前的角度加上该角度
    x: 0,
    y: 0,
    deg1: 0,

x1和y1我用的全局变量。。。因为只有在开始的时候赋值就可以后面不用改变

这里写图片描述

首先感谢这么多人的评论;受宠若惊,始料未及= =!;
应大家的的要求 我把这个功能做了一个组件;本来想做成第三方插件;你们可以直接使用的;但是第三方插件提交的时候遇到了点问题;好像个人开发不能提交第三方开发插件申请,所以暂时先把代码放到github上面了,封装后可以根据你们自己的需求进行设计;如有其它必要、合理的需求可以在本文或者github留言,下面附上github地址

小程序移动、缩放、选择组件

参加比赛的作品,开发周期一个月,使用了 Wafer2 框架,后台采用腾讯云提供的 Node.js SDK 接入对象存储 API ,前端核心代码实现了类似于图片编辑器的功能,支持图片文字的移动旋转缩放、生成预览图以及编辑状态的保存,动画部分采用 CSS 动画实现小程序中的模态输入框部分使用了自己封装的 InputBox 组件代码已移除 AppId 等敏感信息,可自行添加自己的 AppId AppSecret 以配置后台环境,实现登录测试,详细添加方法见下文「使用方法」,若本地运行可通过修改 app.json 文件中 page 字段的顺序来查看不同页面微信小程序定制需求请联系作者微信:aweawds (注明来意)效果展示      使用方法首先点击右上角 Star ʕ •ᴥ•ʔ获取Demo代码执行 git clone https://github.com/goolhanrry/Weapp-Demo-LemonJournal.git或 点击此处 下载最新版本的代码解压后在微信开发者工具中打开 Weapp-Demo-LemonJournal 文件夹即可如需进行登录测试,还要执行以下步骤准备好自己的 AppId AppSecret(可在微信公众平台注册后获取)在 project.config.json 的 appid 字段中填入 AppId在 /client/utils/util.js 中相应位置填入 AppId AppSecret在微信开发者工具中重新导入整个项目,上传后台代码后编译运行即可核心代码组件的移动旋转缩放主要思路是把  标签(对应图片  标签(对应文字)封装在同一个自定义组件  中,通过对外暴露的 text 变量是否为空来进行条件渲染,然后绑定 onTouchStart() 、onTouchEnd()  onTouchMove() 三个事件来对整个组件的位置、角度、大小、层级以及 “旋转 “移除” 两个按钮的行为进行操作onTouchStart: function (e) {     // 若未选中则直接返回     if (!this.data.selected) {         return     }     switch (e.target.id) {         case 'sticker': {             this.touch_target = e.target.id             this.start_x = e.touches[0].clientX * 2             this.start_y = e.touches[0].clientY * 2             break         }         case 'handle': {             // 隐藏移除按钮             this.setData({                 hideRemove: true             })             this.touch_target = e.target.id             this.start_x = e.touches[0].clientX * 2             this.start_y = e.touches[0].clientY * 2             this.sticker_center_x = this.data.stickerCenterX;             this.sticker_center_y = this.data.stickerCenterY;             this.remove_center_x = this.data.removeCenterX;             this.remove_center_y = this.data.removeCenterY;             this.handle_center_x = this.data.handleCenterX;             this.handle_center_y = this.data.handleCenterY;             this.scale = this.data.scale;             this.rotate = this.data.rotate;             break         }     } }, onTouchEnd: function (e) {     this.active()     this.touch_target = ''     // 显示移除按钮     this.setData({         removeCenterX: 2 * this.data.stickerCenterX - this.data.handleCenterX,         removeCenterY: 2 * this.data.stickerCenterY - this.data.handleCenterY,         hideRemove: false     })     // 若点击移除按钮则触发移除事件,否则触发刷新数据事件     if (e.target.id === 'remove') {         this.triggerEvent('removeSticker', this.data.sticker_id)     } else {         this.triggerEvent('refreshData', this.data)     } }, onTouchMove: function (e) {     // 若无选中目标则返回     if (!this.touch_target) {         return     }     var current_x = e.touches[0].clientX * 2     var current_y = e.touches[0].clientY * 2     var diff_x = current_x - this.start_x     var diff_y = current_y - this.start_y     switch (e.target.id) {         case 'sticker': {             // 拖动组件则所有控件同时移动             this.setData({                 stickerCenterX: this.data.stickerCenterX   diff_x,                 stickerCenterY: this.data.stickerCenterY   diff_y,                 removeCenterX: this.data.removeCenterX   diff_x,                 removeCenterY: this.data.removeCenterY   diff_y,                 handleCenterX: this.data.handleCenterX   diff_x,                 handleCenterY: this.data.handleCenterY   diff_y             })             break         }         case 'handle': {             // 拖动操作按钮则原地旋转缩放             this.setData({                 handleCenterX: this.data.handleCenterX   diff_x,                 handleCenterY: this.data.handleCenterY   diff_y             })             var diff_x_before = this.handle_center_x - this.sticker_center_x;             var diff_y_before = this.handle_center_y - this.sticker_center_y;             var diff_x_after = this.data.handleCenterX - this.sticker_center_x;             var diff_y_after = this.data.handleCenterY - this.sticker_center_y;             var distance_before = Math.sqrt(diff_x_before * diff_x_before   diff_y_before * diff_y_before);             var distance_after = Math.sqrt(diff_x_after * diff_x_after   diff_y_after * diff_y_after);             var angle_before = Math.atan2(diff_y_before, diff_x_before) / Math.PI * 180;             var angle_after = Math.atan2(diff_y_after, diff_x_after) / Math.PI * 180;             this.setData({                 scale: distance_after / distance_before * this.scale,                 rotate: angle_after - angle_before   this.rotate             })             break         }     }     this.start_x = current_x;     this.start_y = current_y; }编辑状态的保存一篇手帐包含的组件类型包括 sticker(软件自带的贴纸)、image(用户上传的图片 text(自定义文字)三种,全部保存在一个如下格式的 json 对象中,每个独立组件都包含了一个不重复的 id 以及相关的信息,保存时由客户端生成该对象并编码成 json 字符串存储在数据库,恢复编辑状态时通过解析 json 字符串获得对象,再由编辑页面渲染{     "backgroundId": "5",                                        背景图id     "assemblies": [         {             "id": "jhjg",                                       组件id             "component_type": "image",                          组件类型(自定义图片)             "image_url": "https://example.com/jhjg.png",        图片地址             "stickerCenterX": 269,                              中心横坐标             "stickerCenterY": 664,                              中心纵坐标             "scale": 1.7123667831396403,                        缩放比例             "rotate": -3.0127875041833434,                      旋转角度             "wh_scale": 1,                                      图片宽高比             "z_index": 19                                       组件层级         },         {             "id": "gs47",             "component_type": "text",                           组件类型(文字)             "text": "test",                                     文字内容             "stickerCenterX": 479,             "stickerCenterY": 546,             "scale": 1.808535318980528,             "rotate": 29.11614626607893,             "z_index": 10         },         {             "id": "chjn",             "component_type": "sticker",                        组件类型(贴纸)             "sticker_type": "food",                             贴纸类型             "sticker_id": "1",                                  贴纸id             "image_url": "https://example.com/weapp/stickers/food/1.png",             "stickerCenterX": 277,             "stickerCenterY": 260,             "scale": 1.3984276885130673,             "rotate": -16.620756913892055,             "z_index": 5         }     ] }
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值