微信小程序canvas拖拽、截图组件功能


Posted in Javascript onSeptember 04, 2018

先看下微信小程序canvas拖拽功能

组件地址

github.com/jasondu/wx-… readme近期补上

实现效果

微信小程序canvas拖拽、截图组件功能

如何实现

  1. 使用canvas
  2. 使用movable-view标签

由于movable-view无法实现旋转,所以选择使用canvas

需要解决的问题

  • 如何将多个元素渲染到canvas上
  • 如何知道手指在元素上、如果多个元素重叠如何知道哪个元素在最上层
  • 如何实现拖拽元素
  • 如何缩放、旋转、删除元素

看起来挺简单的嘛,就把上面这几个问题解决了,就可以实现功能了;接下来我们一一解决。

如何将多个元素渲染到canvas上

定义一个DragGraph类,传入元素的各种属性(坐标、尺寸…)实例化后推入一个 渲染数组 里,然后再循环这个数组调用实例中的渲染方法,这样就可以把多个元素渲染到canvas上了。

如何知道手指在元素上、如果多个元素重叠如何知道哪个元素在最上层

在DragGraph类中定义了判断点击位置的方法,我们在canvas上绑定touchstart事件,将手指的坐标传入上面的方法,我们就可以知道手指是点击到元素本身,还是删除图标或者变换大小的图标上了,这个方法具体怎么判断后面会讲解。

通过循环 渲染数组 判断是非点击到哪个元素到,如果点击中了多个元素,也就是多个元素重叠,那第一个元素就是最上层的元素啦。

###如何实现拖拽元素

通过上面我们可以判断手指是否在元素上,当touchstart事件触发时我们记录当前的手指坐标,当touchmove事件触发时,我们也知道这时的坐标,两个坐标取差值,就可以得出元素位移的距离啦,修改这个元素实例的x和y,再重新循环渲染 渲染数组 就可以实现拖拽的功能。

如何缩放、旋转、删除元素

这一步相对比较难一点,我会通过示意图跟大家讲解。

微信小程序canvas拖拽、截图组件功能

我们先讲缩放和旋转

通过touchstart和touchmove我们可以获得旋转前的旋转后的坐标,图中的线A为元素的中点和旋转前点的连线;线B为元素中点和旋转后点的连线;我们只需要求A和B两条线的夹角就可以知道元素旋转的角度。缩放尺寸为A和B两条线长度之差。

计算旋转角度的代码如下:

const centerX = (this.x + this.w) / 2; // 中点坐标
const centerY = (this.y + this.h) / 2; // 中点坐标
const diffXBefore = px - centerX;  // 旋转前坐标
const diffYBefore = py - centerY;  // 旋转前坐标
const diffXAfter = x - centerX;   // 旋转后坐标
const diffYAfter = y - centerY;   // 旋转后坐标
const angleBefore = Math.atan2(diffYBefore, diffXBefore) / Math.PI * 180;
const angleAfter = Math.atan2(diffYAfter, diffXAfter) / Math.PI * 180;
// 旋转的角度
this.rotate = currentGraph.rotate + angleAfter - angleBefore;

计算缩放尺寸的代码如下:

// 放大 或 缩小
this.x = currentGraph.x - (x - px);
this.y = currentGraph.y - (x - px);

下面介绍下小程序canvas截图组件

最近做一个小程序的过程中,需要用到截图功能,网上搜了一下,发现没有符合要求的,就自己搞了个组件,方便复用。

目前功能很简单,传入宽高和图片路径即可,宽高是为了计算截图的比例,只支持缩放和移动。

实现思路是:

1.模拟一个截取框;

2.移动图片位置,缩放图片;

3.获取图片在其中的位置(left,top,width,height);

4.使用canvas绘制图片,然后截取就ok了。

其中第二步的缩放图片比较麻烦,缩放中心点以及平滑缩放

以下是我的实现方式

wxml:

<!--component/picPro/picPro.wxml-->
<scroll-view class='body' hidden="{{hidden}}">
<view class='flex-column flex-between full-height full-width' bindtouchstart="touchstart" bindtouchmove="touchmove" bindtouchend="touchend">
<view class='bg_dark out_item'></view>
<view class='flex-row main flex-between' style='height:{{(windowWidth - margin.left - margin.right)/ratio + "px"}}'>
<view class='bg_dark main_item full-height' style='width:{{margin.left + "px"}}'></view>
<view class='inner relative full-width' id='showArea'>
<image class='absolute img' src='{{src}}' style="width:{{img.width}}px;height:{{img.height}}px;left:{{img.left}}px;top:{{img.top}}px;"></image>
<canvas canvas-id='imgCanvas' class='absolute img_canvas full-height full-width' />
<view class='absolute inner_item left_top'></view>
<view class='absolute inner_item right_top'></view>
<view class='absolute inner_item right_bottom'></view>
<view class='absolute inner_item left_bottom'></view>
</view>
<view class='bg_dark main_item full-height' style='width:{{margin.right + "px"}}'></view>
</view>
<view class='bg_dark out_item flex-column flex-end'>
 <view class='flex-around text_white text_bg'>
<view catchtap='outputImg' data-type='1'><text>重新上传</text></view>
<view catchtap='getImg'><text>选择图片</text></view>
</view> 
</view>
<!-- -->
<view class='absolute full-width full-height bg_black'></view>
</view>
</scroll-view>

wxss:(其中引入了一个公共样式,关于flex布局的,看样式名也能猜到)

/* component/picPro/picPro.wxss */
@import '../../resource/style/flex.wxss';
.body{
 position: fixed;
 top: 0;
 right: 0;
 bottom: 0;
 left: 0;
}
.text_white{
 color: white;
}
.main{
}
.out_item{
 width: 100%;
 height: 100%;
 flex: 1;
}
.bg_dark{
 background-color: rgba(0, 0, 0, 0.85)
}
.main_item{
 width: 15px;
}
.inner{
 outline: 3rpx solid white;
 background-color: rgba(0, 0, 0, 0.12);
 box-shadow: 0 0 4px rgba(0, 0, 0, 0.5) inset;
}
.inner_item{
 width: 8px;
 height: 8px;
}
.inner_item.left_top{
 border-left: 3px solid white;
 border-top: 3px solid white;
 left: -3px;
 top: -3px;
}
.inner_item.right_top{
 border-right: 3px solid white;
 border-top: 3px solid white;
 right: -3px;
 top: -3px;
}
.inner_item.right_bottom{
 border-right: 3px solid white;
 border-bottom: 3px solid white;
 right: -3px;
 bottom: -3px;
}
.inner_item.left_bottom{
 border-left: 3px solid white;
 border-bottom: 3px solid white;
 left: -3px;
 bottom: -3px;
}
.img{
 z-index: -1;
}
.bg_black{
 background-color:black;
 z-index: -2; 
}
.text_bg{
 padding-bottom: 2em;
 font-size: 0.9em;
}
.img_canvas{
 opacity: 0.5;
}
.newImg{
 z-index: 2
}

js:

// component/picPro/picPro.js
const state = {
 // 可用区域body
 window: { width: 0, height: 0 },
 // 原始图片信息
 originImg: { width: 0, height: 0 },
 // 第一次图片缩放信息
 firstScaleImg: { width: 0, height: 0 },
 // 截取区域信息
 interArea: { width: 0, height: 0 },
 // 单手触摸位置
 touchLast: { x: 0, y: 0 },
 // 滑动距离
 touchMove: { x: 0, y: 0 },
 // 滑动离开时图片状态
 moveImgState: {
  width: 0,
  height: 0,
  top: 0,
  left: 0,
 },
 // 双手触摸位置
 touchList: [{ x: 0, y: 0 }, { x: 0, y: 0 }],
 // 图片缩放比例
 scale: 1,
}
Component({
 /**
  * 组件的属性列表
  */
 properties: {
  //宽(非实际值)
  width: {
   type: Number,
   value: 600
  },
  //高
  height: {
   type: Number,
   value: 300
  },
  //图片路径
  src: {
   type: String,
   value: ""
  },
  //显示隐藏
  hidden: {
   type: Boolean,
   value: false
  },
  //截取框的信息
  margin: {
   type: Object,
   value: {
    left: 15,
    right: 15,
    top: 200,
    bottom: 200,
   }
  }
 },

 ready() {
  this.initialize();
  // const canvas = wx.createCanvasContext('imgCanvas', this);
  // canvas.draw(false, () => { console.log('ccc') }, this);
 },

 /**
  * 组件的初始数据
  */
 data: {
  touchRange: 8,
  img: {
   width: 0,
   height: 0,
   top: 0,
   left: 0,
  },
  canvas: {},
  ratio: 0,
  originImg: {
   width: 0,
   height: 0
  }
 },

 /**
  * 组件的方法列表
  */
 methods: {
  touchstart(e) {
   // console.log("touchstart", e);

  },
  touchmove(e) {
   if (e.touches.length === 1) { this.singleSlip(e.touches[0]) } else {
    this.doubleSlip(e.touches)
   }
  },
  touchend(e) {
   // console.log("touchend", e);
   const x = 0, y = 0;
   state.touchLast = { x, y };
   state.touchMove = { x, y };
   state.touchList = [{ x, y }, { x, y }];
   state.moveImgState = this.data.img;
   // console.log(this.data.img);
  },
  // 单手滑动操作
  singleSlip(e) {
   const { clientX: x, clientY: y } = e;
   const that = this;
   if (state.touchLast.x && state.touchLast.y) {
    state.touchMove = { x: x - state.touchLast.x, y: y - state.touchLast.y };
    state.touchLast = { x, y };
    const move = (_x = false, _y = false) => {
     const bottom = that.data.img.height + that.data.img.top;
     const right = that.data.img.width + that.data.img.left;
     const h = state.interArea.height;
     const w = state.interArea.width;
     const param = {};
     if (_x) {
      if (right > w && that.data.img.left < 0) {
       param.left = that.data.img.left + state.touchMove.x * 0.1
      } else if (right <= w && state.touchMove.x > 0) {
       param.left = that.data.img.left + state.touchMove.x * 0.1
      } else if (that.data.img.left >= 0 && state.touchMove.x < 0) {
       param.left = that.data.img.left + state.touchMove.x * 0.1
      }
     };
     if (_y) {
      if (bottom > h && that.data.img.top < 0) {
       param.top = that.data.img.top + state.touchMove.y * 0.1
      } else if (bottom <= h && state.touchMove.y > 0) {
       param.top = that.data.img.top + state.touchMove.y * 0.1
      } else if (that.data.img.top >= 0 && state.touchMove.y < 0) {
       param.top = that.data.img.top + state.touchMove.y * 0.1
      }
     };
     // console.log(param);
     that.setImgPos(param)
    };
    if (state.scale == 1) {
     if (that.data.img.width == state.interArea.width) {
      move(false, true)
     } else {
      move(true, false)
     }
    } else {
     move(true, true)
    }
   } else {
    state.touchLast = { x, y }
   }
  },
  // 双手缩放操作
  doubleSlip(e) {
   const that = this;
   const { clientX: x0, clientY: y0 } = e[0];
   const { clientX: x1, clientY: y1 } = e[1];
   if (state.touchList[0].x && state.touchList[0].y) {
    let changeScale = (Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)) - Math.sqrt((state.touchList[1].x - state.touchList[0].x) * (state.touchList[1].x - state.touchList[0].x) + (state.touchList[1].y - state.touchList[0].y) * (state.touchList[1].y - state.touchList[0].y))) * 0.0005;
    changeScale = changeScale >= 1.5 ? 1.5 : (changeScale <= -1 ? -1 : changeScale);
    state.scale = that.data.img.width / state.firstScaleImg.width < 1 ? 1 : (state.scale > 2.5 ? 2.5 : 1 + changeScale);
    let width = state.firstScaleImg.width * (state.scale - 1) + state.moveImgState.width;
    width = width < state.firstScaleImg.width ? state.firstScaleImg.width : width;
    let height = state.firstScaleImg.height * (state.scale - 1) + state.moveImgState.height;
    height = height < state.firstScaleImg.height ? state.firstScaleImg.height : height;
    let left = width * (1 - state.scale) / 4 + state.moveImgState.left;
    left = left * (-1) > width - state.interArea.width ? state.interArea.width - width: left > 0 ? 0 : left;
    let top = height * (1 - state.scale) / 4 + state.moveImgState.top;
    top = top * (-1) > height - state.interArea.height ?state.interArea.height - height : top > 0 ? 0 : top;
    const setImgObj = { width, height, left, top };
    that.setImgPos(setImgObj)
   } else {
    state.touchList = [{ x: x0, y: y0 }, { x: x1, y: y1 }]
   }
  },
  // 获取可用区域宽高
  getScreenInfo() {
   const that = this;
   return new Promise((resolve, reject) => {
    wx.getSystemInfo({
     success: function (res) {
      const { windowHeight, windowWidth } = res;
      state.window = { windowHeight, windowWidth };
      that.setData({ windowHeight, windowWidth })
      // console.log(state.window);
      resolve(res);
     },
    })
   })
  },
  setShowArea() {
   const that = this;
   const w = state.window.windowWidth - that.data.margin.left - that.data.margin.right;
   const h = (that.data.height / that.data.width) * w;
  },
  outputImg() {
   this.setData({
    hidden: true,
   })
  },
  getImgInfo(path) {
   return new Promise((resolve, reject) => {
    wx.getImageInfo({
     src: path,
     success(res) {
      console.log(res);
      resolve(res);
     },
     fail(err) {
      reject(err)
     }
    })
   })
  },
  // 设置图片
  setImgPos({ width, height, top, left }) {
   width = width || this.data.img.width;
   height = height || this.data.img.height;
   top = top || this.data.img.top;
   left = left || this.data.img.left
   this.setData({
    img: { width, height, top, left }
   })
  },
  // 初始化图片位置大小
  initialize() {
   const that = this;
   const ratio = that.data.width / that.data.height;
   this.getScreenInfo().then(res => {
    console.log(res);
    state.interArea = { width: res.windowWidth - that.data.margin.left - that.data.margin.right + 2, height: (res.windowWidth - that.data.margin.left - that.data.margin.right) / ratio };
    console.log("interArea", state.interArea)
    that.getImgInfo(that.data.src).then(imgInfo => {
     const { width, height } = imgInfo;
     const imgRatio = width / height;
     state.originImg = { width, height };
     that.setData({
      ratio: ratio
     });
     if (imgRatio > ratio) {
      that.setImgPos({
       height: state.interArea.height,
       width: state.interArea.height * imgRatio
      })
     } else {
      that.setImgPos({
       height: state.interArea.width / imgRatio,
       width: state.interArea.width,
      })
     };
     state.firstScaleImg = { width: that.data.img.width, height: that.data.img.height }
    });
   });
  },
  // 截图
  getImg(){
   const that = this;
   // console.log('dudu', that.data.img);
   const canvas = wx.createCanvasContext('imgCanvas', this);
   const {width,height,left,top} = that.data.img;
   const saveImg = ()=>{
    console.log('开始截取图片');
    wx.canvasToTempFilePath({
     canvasId:"imgCanvas",
     success(res){
      // console.log(res);
      that.setData({
       hidden:true,
       // src:""
      });
      that.triggerEvent("putimg", { imgUrl: res.tempFilePath},{});
     },
     fail(err){
      console.log(err)
     }
    },that)
   };
   canvas.drawImage(that.data.src, left, top, width, height);
   canvas.draw(false, () => { saveImg() }, that)
  }
 }
})

引用的时候除了宽高路径以外,需要wx:if;如果不卸载组件,会出现只能截一次的bug

因为小程序里面没有类似vue中catch的观测数据变化的东西,也不想为了个组件专门去搞一个,就用这种方式代替了,嘻嘻,好敷衍。。

总结

以上所述是小编给大家介绍的微信小程序canvas拖拽、截图组件功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
简单选项卡 js和jquery制作方法分享
Feb 26 Javascript
jquery如何扑捉回车键触发的事件
Apr 24 Javascript
jquery实现模拟百分比进度条渐变效果代码
Oct 29 Javascript
程序员必知35个jQuery 代码片段
Nov 05 Javascript
angularjs 表单密码验证自定义指令实现代码
Oct 27 Javascript
js微信支付实现代码
Dec 22 Javascript
vue axios整合使用全攻略
May 24 Javascript
微信小程序定位当前城市的方法
Jul 19 Javascript
通过jQuery学习js类型判断的技巧
May 27 jQuery
在vue中使用echars实现上浮与下钻效果
Nov 08 Javascript
easyUI使用分页过滤器对数据进行分页操作实例分析
Jun 01 Javascript
AngularJs的$http发送POST请求,php无法接收Post的数据问题及解决方案
Aug 13 Javascript
mpvue写一个CPASS小程序的示例
Sep 04 #Javascript
Vue表单及表单绑定方法
Sep 04 #Javascript
elementUI Vue 单个按钮显示和隐藏的变换功能(两种方法)
Sep 04 #Javascript
为jquery的ajax请求添加超时timeout时间的操作方法
Sep 04 #jQuery
vue做移动端适配最佳解决方案(亲测有效)
Sep 04 #Javascript
基于element-ui的rules中正则表达式
Sep 04 #Javascript
微信小程序的部署方法步骤
Sep 04 #Javascript
You might like
php生成WAP页面
2006/10/09 PHP
PHP中读取文件的8种方法和代码实例
2014/08/05 PHP
PHP使用openssl扩展实现加解密方法示例
2020/02/20 PHP
FireFox下XML对象转化成字符串的解决方法
2011/12/09 Javascript
javascript实现文字图片上下滚动的具体实例
2013/06/28 Javascript
jquery为页面增加快捷键示例
2014/01/31 Javascript
NODE.JS加密模块CRYPTO常用方法介绍
2014/06/05 Javascript
javascript模拟实现ajax加载框实例
2014/10/15 Javascript
Jqgrid之强大的表格插件应用
2015/12/02 Javascript
第十篇BootStrap轮播插件使用详解
2016/06/21 Javascript
原生JS获取元素集合的子元素宽度实例
2016/12/14 Javascript
JS简单获取当前日期和农历日期的方法
2017/04/17 Javascript
史上最全JavaScript数组去重的十种方法(推荐)
2017/08/17 Javascript
JavaScript基础之流程控制语句的用法
2017/08/31 Javascript
浅谈Vue.js中ref ($refs)用法举例总结
2017/12/19 Javascript
微信小程序调用摄像头隐藏式拍照功能
2018/08/22 Javascript
vue 使用自定义指令实现表单校验的方法
2018/08/28 Javascript
详解Vue实战指南之依赖注入(provide/inject)
2018/11/13 Javascript
es6中let和const的使用方法详解
2020/02/24 Javascript
浅谈JavaScript中的“!!”作用
2020/08/03 Javascript
[02:00]DAC2018主宣传片——龙征四海,剑问东方
2018/03/20 DOTA
django 自定义用户user模型的三种方法
2014/11/18 Python
Python实现读取并保存文件的类
2017/05/11 Python
python中实现数组和列表读取一列的方法
2018/04/03 Python
python 用for循环实现1~n求和的实例
2019/02/01 Python
python3+selenium自动化测试框架详解
2019/03/17 Python
pytorch实现CNN卷积神经网络
2020/02/19 Python
Django 实现 Websocket 广播、点对点发送消息的代码
2020/06/03 Python
日本小田急百货官网:Odakyu
2018/07/19 全球购物
幼儿教师培训感言
2014/03/08 职场文书
职称评定自我鉴定
2014/03/18 职场文书
努力学习演讲稿
2014/05/10 职场文书
先进党支部申报材料
2014/12/24 职场文书
AI:如何训练机器学习的模型
2021/04/16 Python
Vue实现导入Excel功能步骤详解
2021/07/03 Vue.js
Python matplotlib可视化之绘制韦恩图
2022/02/24 Python