主要是参考下面的链接,加上一些自己的改进理解和 RxJS 版的实现。
文章的作者好像是 Fabric.js 的作者
基础版本
下面是基础版本的画图代码,核心是监听几个鼠标事件。
var el = document.getElementById('c');
var ctx = el.getContext('2d');
var isDrawing;
el.onmousedown = function(e) {
isDrawing = true;
ctx.moveTo(e.clientX, e.clientY);
};
el.onmousemove = function(e) {
if (isDrawing) {
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
}
};
el.onmouseup = function() {
isDrawing = false;
};
需要改变画图线条,则需要设置 ctx 的一些属性
ctx.lineWidth = 10;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.shadowBlur = 10;
ctx.shadowColor = 'rgb(0, 0, 0)';
参考示例链接: codepen.io/kangax/pen/…
上面画法存在的问题是:开始画的线条部分有点细和模糊,由于阴影部分的交叠,结束部分变粗。
基础版本改进
基于点的方法
参考示例链接: codepen.io/kangax/pen/…
这个方法是每次重新把之前的路径渲染一次,不是上面提到的画法,一点点的画线条。
鼠标移动时
- 每次移动的时候清除画布
ctx.clearRect(xxx) - 存储路径数组
- 开始画图
ctx.beginPath() - 遍历数组,配合
ctx.lineTo(x, y)画线 - 最后画出线条
ctx.stroke()
贝塞尔曲线版 主要是利用 ctx.quadraticCurveTo。
参考示例链接: codepen.io/kangax/pen/…
改进点的位置计算
参考文章中上面的代码存在一个问题: 点的位置计算,没有考虑 canvas 在文档中的位置。如果 canvas 元素不是在页面的开始位置,线条的位置就不对了。例如设置 canvas 左边距为 margin-left: 200px;
这里需要改进一下 x, y 值的计算方式
function getMousePos(canvas, evt) {
const rect = canvas.getBoundingClientRect();
return {
x: (evt.clientX - rect.left) / (rect.right - rect.left) * canvas.width,
y: (evt.clientY - rect.top) / (rect.bottom - rect.top) * canvas.height
}
}
// 也可以是
// x: evt.clientX - rect.left
使用 RxJS 来画图
三个数据流: 按下鼠标,鼠标移动,松开鼠标
mouseDown$: X-------
mouseMove$: -A-B-C-D
mouseUp$: --------U-
mouseDown$.pipe(switchMap(xx)) 这样每次按下鼠标就会触发新的数据流分支
mouseMove$.pipe(takeUntil(xxx), pairwise()) 把触发松开鼠标之前的数据都取下,然后利用 pairwise 数据流变成了 -[A,B]-[B,C]-[C,D]-[D,U]-
const { fromEvent } = rxjs;
const {
map,
takeUntil,
pairwise,
switchMap
} = rxjs.operators;
const target = document.querySelector('#c')
const rect = target.getBoundingClientRect();
const ctx = target.getContext('2d');
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.strokeStyle = '#000';
const mouseDown$ = fromEvent(target, 'mousedown')
const mouseMove$ = fromEvent(target, 'mousemove')
const mouseUp$ = fromEvent(target, 'mouseup')
mouseDown$.pipe(
switchMap(e => {
return mouseMove$.pipe(
takeUntil(mouseUp$),
pairwise()
)
})
).subscribe(([preEvt, curEvt]) => {
if (preEvt) {
ctx.beginPath();
const pre = {
x: preEvt.clientX - rect.left,
y: preEvt.clientY - rect.top
}
const cur = {
x: curEvt.clientX - rect.left,
y: curEvt.clientY - rect.top
}
ctx.moveTo(pre.x, pre.y)
ctx.lineTo(cur.x, cur.y)
ctx.stroke()
}
});