小程序canvas 缩放/拖动/还原/封装和实例
一、预览
之前写过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,// 节流绘图间隔,单位mspixelRatio: wx.getSystemInfoSync().pixelRatio,// 像素比(高像素比可以解决高清屏幕模糊问题)}this.canvasDraw =newCanvasDraw(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><viewclass="canvas-wrap"><canvastype="2d"id="canvas"class="canvas"disable-scroll="true"@touchstart="touchstart"@touchmove="touchmove"@touchend="touchend"@tap="tap"></canvas></view></template><script>import{ CanvasDraw }from'./canvasDraw'exportdefault{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,// 节流绘图间隔,单位mspixelRatio: wx.getSystemInfoSync().pixelRatio,// 像素比(高像素比可以解决高清屏幕模糊问题)}this.canvasDraw =newCanvasDraw(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><stylescoped>.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
*/exportfunctionCanvasDraw(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// 节流绘图间隔,单位msthis.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)returnthis.setDefault()// 单指if(fingers ===1){
startPoint.x = e.touches[0].x
startPoint.y = e.touches[0].y
}elseif(fingers ===2){
startDistance =this.get2PointsDistance(e)}}/**
* 触摸移动
*/this.touchmove=(e)=>{if(fingers >2)returnif(this.isThrottleDraw){if(drawTimer)returnthis.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()}elseif(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 =nullthis.canvasNode =nullthis.ctx =nullthis.touchMoveEvent =null
option.drawCallBack =null}/**
* 获取2触摸点距离
* @param {object} e 触摸对象
* @returns 2触摸点距离
*/this.get2PointsDistance=(e)=>{if(e.touches.length <2)return0const 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)}}exportdefault CanvasDraw
兄弟,如果帮到你,点个赞再走
版权归原作者 iamlujingtao 所有, 如有侵权,请联系我们删除。