您好,欢迎来到好走旅游网。
搜索
您的当前位置:首页微信小程序canvas拖拽、截图组件功能

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

来源:好走旅游网

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

    Copyright © 2019- haog.cn 版权所有 赣ICP备2024042798号-2

    违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

    本站由北京市万商天勤律师事务所王兴未律师提供法律服务