canvas画笔功能的实现

一,基础的实现

我自己封装了个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()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值