我自己封装了个canvas的库,这部分可以不看,主要看实现画笔的思路吧.
<canvas
canvas-id="poster-canvas"
class="poster-canvas"
style="border: 1px solid #ccc;"
:style="{ width: canvasW + 'px', height: canvasH + 'px' }"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend"
></canvas>
先拿到canas对象:
this.canvas = new biuCanvas.Canvas('poster-canvas',this)
因为有画笔工具,有折线工具,所以我需要两个状态:
drawType:'line',//画笔绘图类型
positions:[]//点坐标集合
画笔开始绘制的代码如下:
touchstart(e){
let point={}
switch (this.drawType){
case 'polyline':
//折线
case 'line':
point.x=e.changedTouches[0].x
point.y=e.changedTouches[0].y
this.positions.push(point)
break;
default:
break;
}
},
touchmove(e){
let point={}
switch (this.drawType){
case 'line':
point.x=e.changedTouches[0].x
point.y=e.changedTouches[0].y
this.positions.push(point)
if(this.positions.length>1){
// 画笔绘制
let paintBrush = new biuCanvas.PaintBrush(JSON.parse(JSON.stringify(this.positions)))
// 将折线添加到画布中
this.canvas.add(paintBrush)
}
break;
default:
break;
}
},
touchend(e){
switch (this.drawType){
case 'polyline':
//折线
break;
case 'line':
this.positions=[]
break;
default:
break;
}
}
封装的画笔绘制:
biuCanvas.PaintBrush=class {
constructor(arr) {
this.arr=arr
}
render(canvas) {
let ctx=canvas.context
ctx.beginPath()
for(let i=0;i<this.arr.length;i++){
if(i==0){
ctx.moveTo(this.arr[i].x, this.arr[i].y)
}else{
ctx.lineTo(this.arr[i].x, this.arr[i].y)
}
}
ctx.stroke()
}
}
对canvas的封装,这里是我自己写的,主要实现就是在this.canvas.add(paintBrush)的时候,能够执行paintBrush的render,从而绘制图案罢了.
biuCanvas.Canvas = class {
constructor(canvasId,_this,options={}) {
this.canvasId = canvasId;
this.context = uni.createCanvasContext(canvasId, _this);
this.width = options.width || 300;
this.height = options.height || 400;
this.objects = [];
}
add(object) {
//在每次add的时候,应该修改currenStatus,然后object['pathStatus']保存这个status
this.drawStatus.setCurrentStatus(object.options)
//在初始化的时候,会给该Canvas实例对象新增属性drawStatus来记录当前绘图状态管理器,现在每次新增图案需要存储当前绘图状态
object['pathStatus']=JSON.parse(JSON.stringify(this.drawStatus.currentStatus))
//对于一次绘制:如折线,每多次点击,都是一次绘制,不能用push,而是应该用覆盖
if((object instanceof biuCanvas.Polyline||object instanceof biuCanvas.PaintBrush) && object.arr && object.arr.length>2){
this.objects.pop();
}
this.objects.push(object);
this.render();
}
render() {
for (let i = 0; i < this.objects.length; i++) {
this.drawStatus.setStatus(this.objects[i]['pathStatus'],this.context)
this.objects[i].render(this);
}
this.context.draw()
}
}
到目前为止,已经能简单绘制图案了,其实思路很简单,就是先用moveTo到第一个起点,然后后续的点都使用lineTo来绘制.
但是目前还很简陋,存在许多问题需要优化.
上文使用tochmove来采集点,太多了,性能很差,我们可以根据采集点的距离来筛选一些有效的点.
定义距离判断函数:
distance(e,points){
const {x:pointX,y:pointY}=e.changedTouches[0]
const {x:lastX,y:lastY}=points[points.length-1]
const dis=(lastY-pointY)*(lastY-pointY)+(lastX-pointX)*(lastX-pointX)
if(dis>300){
return true
}else{
return false
}
}
更改tochmove方法:
touchmove(e){
let point={}
switch (this.drawType){
case 'line':
if(this.distance(e,this.positions)){
point.x=e.changedTouches[0].x
point.y=e.changedTouches[0].y
this.positions.push(point)
if(this.positions.length>1){
// 画笔绘制
let paintBrush = new biuCanvas.PaintBrush(JSON.parse(JSON.stringify(this.positions)))
// 将折线添加到画布中
this.canvas.add(paintBrush)
}
}
break;
default:
break;
}
},
这样就能用更少的点绘制了.
现在的线段,其实是一段段小的折线,并不美观,我需要的是曲线的连接,这里我采用的是二次贝塞尔曲线连接.
对于abcd四个点
bc之间先计算出中点b1
然后b作为控制点,b1作为终点
以此类推
实现的代码主要是:
let ctx=canvas.context
ctx.beginPath()
for(let i=0;i<this.arr.length;i++){
const { x: endX, y: endY } = this.arr[i]
if(i==0){
ctx.moveTo(endX, endY)
}else{
const { x: centerX, y: centerY } = this.arr[i - 1]//作为贝塞尔曲线的控制点
const lastX = (endX + centerX) / 2
const lastY = (endY + centerY) / 2//最后两点的中点作为贝塞尔曲线的终点
ctx.quadraticCurveTo(centerX, centerY, lastX, lastY)
}
}
ctx.stroke()