Canvas 和 RxJS 画图二三事

主要是参考下面的连接,加上一些本身的改进理解和 RxJS 版的实现。javascript

文章的做者好像是 Fabric.js 的做者html

exploring-canvas-drawing-techniquesjava

基础版本

下面是基础版本的画图代码,核心是监听几个鼠标事件。git

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 的一些属性github

ctx.lineWidth = 10;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.shadowBlur = 10;
ctx.shadowColor = 'rgb(0, 0, 0)';
复制代码

参考示例连接: codepen.io/kangax/pen/…canvas

上面画法存在的问题是:开始画的线条部分有点细和模糊,因为阴影部分的交叠,结束部分变粗。数组

基础版本改进

基于点的方法markdown

参考示例连接: codepen.io/kangax/pen/…oop

这个方法是每次从新把以前的路径渲染一次,不是上面提到的画法,一点点的画线条。spa

鼠标移动时

  • 每次移动的时候清除画布 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()
  }
});

复制代码

参考连接

exploring-canvas-drawing-techniques

Element.getBoundingClientRect() - Web API 接口参考 | MDN

相关文章
相关标签/搜索