当前位置:首页 » 《随便一记》 » 正文

小程序canvas 缩放/拖动/还原/封装和实例--开箱即用

6 人参与  2022年12月15日 14:10  分类 : 《随便一记》  评论

点击全文阅读


小程序canvas 缩放/拖动/还原/封装和实例

一、预览二、使用2.1 创建和配置方法 三、源码3.1 实例组件3.2 核心类

一、预览

之前写过web端的canvas 缩放/拖动/还原/封装和实例。最近小程序也需要用到,但凡是涉及小程序canvas还是比较多坑的,而且难用多了,于是在web的基础上重新写了小程序的相关功能。实现功能有:

支持双指、按钮缩放支持触摸拖动支持高清显示支持节流绘图支持还原、清除画布内置简化绘图方法

效果如下:
请添加图片描述

二、使用

案例涉及到2个文件,一个是绘图组件canvas.vue,另一个是canvasDraw.js,核心是canvasDraw.js里定义的CanvasDraw类

2.1 创建和配置

小程序获取#canvas对象后就可以创建CanvasDraw实例了,创建实例时可以根据需要设置各种配置,其中drawCallBack是必须的,是用户自定义的绘图方法,程序会在this.canvasDraw.draw()后再回调drawCallBack()来实现用户的绘图。
拖动、缩放画布都会调用this.canvasDraw.draw()。

    /** 初始化canvas */    initCanvas() {      const query = wx.createSelectorQuery().in(this)      query        .select('#canvas')        .fields({ node: true, size: true, rect: true })        .exec((res) => {          const ele = res[0]          this.canvasEle = ele          // 配置项          const option = {            ele: this.canvasEle, // canvas元素            drawCallBack: this.draw, // 必须:用户自定义绘图方法            scale: 1, // 当前缩放倍数            scaleStep: 0.1, // 缩放步长(按钮)            touchScaleStep: 0.005, // 缩放步长(手势)            maxScale: 2, // 缩放最大倍数(缩放比率倍数)            minScale: 0.5, // 缩放最小倍数(缩放比率倍数)            translate: { x: 0, y: 0 }, // 默认画布偏移            isThrottleDraw: true, // 是否开启节流绘图(建议开启,否则安卓调用频繁导致卡顿)            throttleInterval: 20, // 节流绘图间隔,单位ms            pixelRatio: wx.getSystemInfoSync().pixelRatio, // 像素比(高像素比可以解决高清屏幕模糊问题)          }          this.canvasDraw = new CanvasDraw(option) // 创建CanvasDraw实例后就可以使用实例的所有方法了          this.canvasDraw.draw() // 可以按实际需要调用绘图方法        })    },

方法

canvasDraw.draw() // 绘图canvasDraw.clear() // 清除画布canvasDraw.reset() // 重置画布(恢复到第一次绘制的状态)canvasDraw.zoomIn() // 中心放大canvasDraw.zoomOut() // 中心缩小canvasDraw.zoomTo(scale, zoomCenter) // 缩放到指定倍数(可指定缩放中心点)canvasDraw.destory() // 销毁canvasDraw.drawShape(opt) // 内置简化绘制多边形方法canvasDraw.drawLines(opt) // 内置简化绘制多线段方法canvasDraw.drawText(opt) // 内置简化绘制文字方法

三、源码

3.1 实例组件

canvas.vue

<template>  <view class="canvas-wrap">    <canvas      type="2d"      id="canvas"      class="canvas"      disable-scroll="true"      @touchstart="touchstart"      @touchmove="touchmove"      @touchend="touchend"      @tap="tap"    ></canvas>  </view></template><script>import { CanvasDraw } from './canvasDraw'export default {  data() {    this.canvasDraw = null // 绘图对象    this.canvasEle = null // canvas元素对象    return {}  },  created() {},  beforeDestroy() {    /** 销毁对象 */    if (this.canvasDraw) {      this.canvasDraw.destroy()      this.canvasDraw = null    }  },  mounted() {    /** 初始化 */    this.initCanvas()  },  methods: {    /** 初始化canvas */    initCanvas() {      const query = wx.createSelectorQuery().in(this)      query        .select('#canvas')        .fields({ node: true, size: true, rect: true })        .exec((res) => {          const ele = res[0]          this.canvasEle = ele          // 配置项          const option = {            ele: this.canvasEle, // canvas元素            drawCallBack: this.draw, // 必须:用户自定义绘图方法            scale: 1, // 当前缩放倍数            scaleStep: 0.1, // 缩放步长(按钮)            touchScaleStep: 0.005, // 缩放步长(手势)            maxScale: 2, // 缩放最大倍数(缩放比率倍数)            minScale: 0.5, // 缩放最小倍数(缩放比率倍数)            translate: { x: 0, y: 0 }, // 默认画布偏移            isThrottleDraw: true, // 是否开启节流绘图(建议开启,否则安卓调用频繁导致卡顿)            throttleInterval: 20, // 节流绘图间隔,单位ms            pixelRatio: wx.getSystemInfoSync().pixelRatio, // 像素比(高像素比可以解决高清屏幕模糊问题)          }          this.canvasDraw = new CanvasDraw(option) // 创建CanvasDraw实例后就可以使用实例的所有方法了          this.canvasDraw.draw() // 可以按实际需要调用绘图方法        })    },    /** 用户自定义绘图内容 */    draw() {      // 默认绘图方式-圆形      const { ctx } = this.canvasDraw      ctx.beginPath()      ctx.strokeStyle = '#f00'      ctx.arc(150, 150, 120, 0, 2 * Math.PI)      ctx.stroke()      // 组件方法-绘制多边形      const shapeOption = {        points: [          { x: 127, y: 347 },          { x: 151, y: 304 },          { x: 173, y: 344 },          { x: 214, y: 337 },          { x: 184, y: 396 },          { x: 143, y: 430 },          { x: 102, y: 400 },        ],        fillStyle: '#00f',      }      this.canvasDraw.drawShape(shapeOption)      // 组件方法-绘制多线段      const linesOption = {        points: [          { x: 98, y: 178 },          { x: 98, y: 212 },          { x: 157, y: 236 },          { x: 208, y: 203 },          { x: 210, y: 165 },        ],        strokeStyle: '#0f0',      }      this.canvasDraw.drawLines(linesOption)      // 组件方法-绘制文字      const textOption = {        text: '组件方法-绘制文字',        isCenter: true,        point: { x: 150, y: 150 },        fillStyle: '#000',      }      this.canvasDraw.drawText(textOption)    },    /** 中心放大 */    zoomIn() {      this.canvasDraw.zoomIn()    },    /** 中心缩小 */    zoomOut() {      this.canvasDraw.zoomOut()    },    /** 重置画布(回复初始效果) */    reset() {      this.canvasDraw.reset()    },    /** 事件绑定 */    tap(e) {      const p = {        x: (e.detail.x - this.canvasEle.left) / this.canvasDraw.scale,        y: (e.detail.y - this.canvasEle.top) / this.canvasDraw.scale,      }      console.log('点击坐标:', p)    },    touchstart(e) {      this.canvasDraw.touchstart(e)    },    touchmove(e) {      this.canvasDraw.touchmove(e)    },    touchend(e) {      this.canvasDraw.touchend(e)    },  },}</script><style scoped>.canvas-wrap {  position: relative;  flex: 1;  width: 100%;  height: 100%;}.canvas {  width: 100%;  flex: 1;}</style>

3.2 核心类

canvasDraw.js

/** * @Author: 大话主席 * @Description: 自定义小程序绘图类 *//** * 绘图类 * @param {object} option */export function CanvasDraw(option) {  if (!option.ele) {    console.error('canvas对象不存在')    return  }  if (!option.drawCallBack) {    console.error('缺少必须配置项:drawCallBack')    return  }  const { ele } = option  /** 外部可访问属性 */  this.canvasNode = ele.node // wx的canvas节点  this.canvasNode.width = ele.width // 设置canvas节点宽度  this.canvasNode.height = ele.height // 设置canvas节点高度  this.ctx = this.canvasNode.getContext('2d')  this.zoomCenter = { x: ele.width / 2, y: ele.height / 2 } // 缩放中心点  this.touchMoveEvent = null // 触摸移动事件  /** 内部使用变量 */  let startPoint = { x: 0, y: 0 } // 拖动开始坐标  let startDistance = 0 // 拖动开始时距离(二指缩放)  let curTranslate = {} // 当前偏移  let curScale = 1 // 当前缩放  let preScale = 1 // 上次缩放  let drawTimer = null // 绘图计时器,用于节流  let touchEndTimer = null // 触摸结束计时器,用于节流  let fingers = 1 // 手指触摸个数  /**   * 根据像素比重设canvas尺寸   */  this.resetCanvasSize = () => {    this.canvasNode.width = ele.width * this.pixelRatio    this.canvasNode.height = ele.height * this.pixelRatio  }  /**   * 初始化   */  this.init = () => {    const optionCopy = JSON.parse(JSON.stringify(option))    this.scale = optionCopy.scale ?? 1 // 当前缩放倍数    this.scaleStep = optionCopy.scaleStep ?? 0.1 // 缩放步长(按钮)    this.touchScaleStep = optionCopy.touchScaleStep ?? 0.005 // 缩放步长(手势)    this.maxScale = optionCopy.maxScale ?? 2 // 缩放最大倍数(缩放比率倍数)    this.minScale = optionCopy.minScale ?? 0.5 // 缩放最小倍数(缩放比率倍数)    this.translate = optionCopy.translate ?? { x: 0, y: 0 } // 默认画布偏移    this.isThrottleDraw = optionCopy.isThrottleDraw ?? true // 是否开启节流绘图(建议开启,否则安卓调用频繁导致卡顿)    this.throttleInterval = optionCopy.throttleInterval ?? 20 // 节流绘图间隔,单位ms    this.pixelRatio = optionCopy.pixelRatio ?? 1 // 像素比(高像素比解决高清屏幕模糊问题)    startPoint = { x: 0, y: 0 } // 拖动开始坐标    startDistance = 0 // 拖动开始时距离(二指缩放)    curTranslate = JSON.parse(JSON.stringify(this.translate)) // 当前偏移    curScale = this.scale // 当前缩放    preScale = this.scale // 上次缩放    drawTimer = null // 绘图计时器,用于节流    fingers = 1 // 手指触摸个数    this.resetCanvasSize()  }  this.init()  /**   * 绘图(会进行缩放和位移)   */  this.draw = () => {    this.clear()    this.ctx.translate(this.translate.x * this.pixelRatio, this.translate.y * this.pixelRatio)    this.ctx.scale(this.scale * this.pixelRatio, this.scale * this.pixelRatio)    // console.log('当前位移', this.translate.x, this.translate.y, '当前缩放倍率', this.scale)    option.drawCallBack()    drawTimer = null  }  /**   * 设置默认值(   */  this.setDefault = () => {    curTranslate.x = this.translate.x    curTranslate.y = this.translate.y    curScale = this.scale    preScale = this.scale  }  /**   * 清除画布(重设canvas尺寸会清空地图并重置canvas内置的scale/translate等)   */  this.clear = () => {    this.resetCanvasSize()  }  /**   * 绘制多边形   */  this.drawShape = (opt) => {    this.ctx.beginPath()    this.ctx.lineWidth = '1'    this.ctx.fillStyle = opt.isSelect ? opt.HighlightfillStyle : opt.fillStyle    this.ctx.strokeStyle = opt.HighlightStrokeStyle    for (let i = 0; i < opt.points.length; i++) {      const p = opt.points[i]      if (i === 0) {        this.ctx.moveTo(p.x, p.y)      } else {        this.ctx.lineTo(p.x, p.y)      }    }    this.ctx.closePath()    if (opt.isSelect) {      this.ctx.stroke()    }    this.ctx.fill()  }  /**   * 绘制多条线段   */  this.drawLines = (opt) => {    this.ctx.beginPath()    this.ctx.strokeStyle = opt.strokeStyle    for (let i = 0; i < opt.points.length; i++) {      const p = opt.points[i]      if (i === 0) {        this.ctx.moveTo(p.x, p.y)      } else {        this.ctx.lineTo(p.x, p.y)      }    }    this.ctx.stroke()  }  /**   * 绘制文字   */  this.drawText = (opt) => {    this.ctx.fillStyle = opt.isSelect ? opt.HighlightfillStyle : opt.fillStyle    if (opt.isCenter) {      this.ctx.textAlign = 'center'      this.ctx.textBaseline = 'middle'    }    this.ctx.fillText(opt.text, opt.point.x, opt.point.y)  }  /**   * 重置画布(恢复到第一次绘制的状态)   */  this.reset = () => {    this.init()    this.draw()  }  /**   * 中心放大   */  this.zoomIn = () => {    this.zoomTo(this.scale + this.scaleStep)  }  /**   * 中心缩小   */  this.zoomOut = () => {    this.zoomTo(this.scale - this.scaleStep)  }  /**   * 缩放到指定倍数   * @param {number} scale 缩放大小   * @param {object} zoomCenter 缩放中心点(可选   */  this.zoomTo = (scale, zoomCenter0) => {    // console.log('缩放到:', scale, '缩放中心点:', zoomCenter0)    this.scale = scale    this.scale = this.scale > this.maxScale ? this.maxScale : this.scale    this.scale = this.scale < this.minScale ? this.minScale : this.scale    const zoomCenter = zoomCenter0 || this.zoomCenter    this.translate.x = zoomCenter.x - ((zoomCenter.x - this.translate.x) * this.scale) / preScale    this.translate.y = zoomCenter.y - ((zoomCenter.y - this.translate.y) * this.scale) / preScale    this.draw()    preScale = this.scale    curTranslate.x = this.translate.x    curTranslate.y = this.translate.y  }  /**   * 触摸开始   */  this.touchstart = (e) => {    fingers = e.touches.length    if (fingers > 2) return    this.setDefault()    // 单指    if (fingers === 1) {      startPoint.x = e.touches[0].x      startPoint.y = e.touches[0].y    } else if (fingers === 2) {      startDistance = this.get2PointsDistance(e)    }  }  /**   * 触摸移动   */  this.touchmove = (e) => {    if (fingers > 2) return    if (this.isThrottleDraw) {      if (drawTimer) return      this.touchMoveEvent = e      drawTimer = setTimeout(this.touchmoveSelf, this.throttleInterval)    } else {      this.touchMoveEvent = e      this.touchmoveSelf()    }  }  /**   * 触摸移动实际执行   */  this.touchmoveSelf = () => {    const e = this.touchMoveEvent    // 单指移动    if (fingers === 1) {      this.translate.x = curTranslate.x + (e.touches[0].x - startPoint.x)      this.translate.y = curTranslate.y + (e.touches[0].y - startPoint.y)      this.draw()    } else if (fingers === 2 && e.touches.length === 2) {      // 双指缩放      const newDistance = this.get2PointsDistance(e)      const distanceDiff = newDistance - startDistance      const zoomCenter = {        x: (e.touches[0].x + e.touches[1].x) / 2,        y: (e.touches[0].y + e.touches[1].y) / 2,      }      this.zoomTo(curScale + this.touchScaleStep * distanceDiff, zoomCenter)    } else {      drawTimer = null    }  }  /**   * 触摸结束   */  this.touchend = () => {    if (this.isThrottleDraw) {      touchEndTimer = setTimeout(this.setDefault, this.throttleInterval)    } else {      this.setDefault()    }  }  /**   * 销毁   */  this.destroy = () => {    clearTimeout(drawTimer)    clearTimeout(touchEndTimer)    drawTimer = null    touchEndTimer = null    this.canvasNode = null    this.ctx = null    this.touchMoveEvent = null    option.drawCallBack = null  }  /**   * 获取2触摸点距离   * @param {object} e 触摸对象   * @returns 2触摸点距离   */  this.get2PointsDistance = (e) => {    if (e.touches.length < 2) return 0    const xMove = e.touches[1].x - e.touches[0].x    const yMove = e.touches[1].y - e.touches[0].y    return Math.sqrt(xMove * xMove + yMove * yMove)  }}export default CanvasDraw

兄弟,如果帮到你,点个赞再走


点击全文阅读


本文链接:http://zhangshiyu.com/post/49793.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1