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

相關文章
相關標籤/搜索