视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
微信小程序canvas拖拽、截图组件功能
2020-11-27 22:08:27 责编:小采
文档


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

组件地址

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

实现效果

如何实现

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

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

需要解决的问题

  • 如何将多个元素渲染到canvas上
  • 如何知道手指在元素上、如果多个元素重叠如何知道哪个元素在最上层
  • 如何实现拖拽元素
  • 如何缩放、旋转、删除元素
  • 看起来挺简单的嘛,就把上面这几个问题解决了,就可以实现功能了;接下来我们一一解决。

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

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

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

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

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

    ###如何实现拖拽元素

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

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

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

    我们先讲缩放和旋转

    通过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拖拽、截图组件功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

    下载本文
    显示全文
    专题