微信小程序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操作textarea 常用方法总结
Dec 03 Javascript
jquery交替变换颜色的三种方法 实例代码
Nov 19 Javascript
javascript中的括号()用法小结
Apr 14 Javascript
jquery中 $.expr使用实例介绍
Jun 09 Javascript
jQuery插件bxSlider实现响应式焦点图
Apr 12 Javascript
使用JQuery选择HTML遍历函数的方法
Sep 17 Javascript
Restify中接入Socket.io报Error:Can’t set headers的错误解决
Mar 28 Javascript
基于Bootstrap模态对话框只加载一次 remote 数据的解决方法
Jul 09 Javascript
父组件中vuex方法更新state子组件不能及时更新并渲染的完美解决方法
Apr 25 Javascript
vue中子组件调用兄弟组件方法
Jul 06 Javascript
js动态生成表格(节点操作)
Jan 12 Javascript
用React Native制作一个简单的游戏引擎
May 27 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中使用Oracle数据库(3)
2006/10/09 PHP
PHP获取MAC地址的具体实例
2013/12/13 PHP
discuz免激活同步登入代码修改方法(discuz同步登录)
2013/12/24 PHP
全面解析PHP操作Memcache基本函数
2016/07/14 PHP
php 修改上传文件大小限制实例详解
2016/10/23 PHP
PHP执行普通shell命令流程解析
2020/08/24 PHP
JavaScript 页面编码与浏览器类型判断代码
2010/06/03 Javascript
Jvascript学习实践案例(开发常用)
2012/06/25 Javascript
js如何取消事件冒泡
2013/09/23 Javascript
jquery实现省市select下拉框的替换(示例代码)
2014/02/22 Javascript
浅析JS中document对象的一些重要属性
2014/03/06 Javascript
页面刷新时记住滚动条的位置jquery代码
2014/06/17 Javascript
js实现图片无缝滚动特效
2020/03/19 Javascript
用Angular实时获取本地Localstorage数据,实现一个模拟后台数据登入的效果
2016/11/09 Javascript
React Native 集成jpush-react-native的示例代码
2017/08/16 Javascript
element-ui 关于获取select 的label值方法
2018/08/24 Javascript
Vue中div contenteditable 的光标定位方法
2018/08/25 Javascript
VsCode与Node.js知识点详解
2019/09/05 Javascript
js回调函数原理与用法案例分析
2020/03/04 Javascript
js实现无缝轮播图
2020/03/09 Javascript
javascript实现的图片预览和上传功能示例【兼容IE 9】
2020/05/01 Javascript
vue+Element中table表格实现可编辑(select下拉框)
2020/05/21 Javascript
vue + node如何通过一个Txt文件批量生成MP3并压缩成Zip
2020/06/02 Javascript
python3序列化与反序列化用法实例
2015/05/26 Python
python删除特定文件的方法
2015/07/30 Python
Python 遍历列表里面序号和值的方法(三种)
2017/02/17 Python
Python对List中的元素排序的方法
2018/04/01 Python
Python中pandas模块DataFrame创建方法示例
2018/06/20 Python
Python函数递归调用实现原理实例解析
2020/08/11 Python
利用HTML5+CSS3实现3D转换效果实例详解
2017/05/02 HTML / CSS
html5表单及新增的改良元素详解
2016/06/07 HTML / CSS
Servlet方面面试题
2016/09/28 面试题
优秀教师事迹简介
2014/02/02 职场文书
我的中国梦演讲稿小学篇
2014/08/19 职场文书
筑梦中国心得体会
2016/01/18 职场文书
pytorch交叉熵损失函数的weight参数的使用
2021/05/24 Python