成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

我從 fabric.js 中學(xué)到了什么

oogh / 1157人閱讀

摘要:前言熟悉的朋友想必都使用或者聽說過,算是一個(gè)元老級(jí)的庫(kù)了,從第一個(gè)版本發(fā)布到現(xiàn)在,已經(jīng)有年時(shí)間了。中緩存是默認(rèn)開啟的,同時(shí)也可以設(shè)置為禁用。處理屏屏幕模糊的問題,直接給出處理方法,就不展開說了。

前言

熟悉 canvas 的朋友想必都使用或者聽說過 Fabric.js,F(xiàn)abric 算是一個(gè)元老級(jí)的 canvas 庫(kù)了,從第一個(gè)版本發(fā)布到現(xiàn)在,已經(jīng)有 8 年時(shí)間了。我近一年時(shí)間也在項(xiàng)目中使用,作為用戶簡(jiǎn)單說說感受:

方便,只有想不到,沒有做不到

源碼寫的真好,代碼規(guī)范,注釋清晰

社區(qū)真匱乏,國(guó)內(nèi)資源尤其少

看文檔不如看源碼

優(yōu)缺點(diǎn)都很鮮明,但總的來說,如果你要做一個(gè)在線編輯類的項(xiàng)目,比如在線 PPT,在線制圖等應(yīng)用,fabric 絕對(duì)是個(gè)很好的選擇。

那么這一系列文章要寫什么?這里不會(huì)主要介紹如何使用 fabric,主要寫的內(nèi)容是把在閱讀源碼過程中,把涉及到原理相關(guān)的知識(shí)總結(jié)出來,比如相關(guān)圖形學(xué)知識(shí)、canvas 相關(guān)、fabric 中的設(shè)計(jì)思想等的相關(guān)知識(shí)。所以,如果你現(xiàn)在還對(duì) fabric 不是很了解,建議先去官網(wǎng)找?guī)讉€(gè) demo 試一下。

下面我們進(jìn)入這次的正題,這篇文章主要介紹 fabric.canvas 涉及到的部分內(nèi)容。

從創(chuàng)建畫布開始

fabric 創(chuàng)建畫布很簡(jiǎn)單:

const canvas = new fabric.Canvas("domId", options);

在這樣一行代碼背后,fabric 主要做了下面這幾件事情:

創(chuàng)建緩存 canvas

構(gòu)建兩層 canvas 元素:lower-canvas 和 upper-canvas

綁定事件

處理 retina 屏

...

下面我把相關(guān)內(nèi)容一一闡述。

canvas 緩存

介紹 canvas 緩存,fabric 中的緩存也是類似的道理,簡(jiǎn)單來說,就是使用一個(gè)離屏 canvas 來做預(yù)渲染,在真實(shí)畫布上用 drawImage 代替直接繪制圖形。

我們先來看個(gè) 例子,大家可以把 FPS meter 打開,切換按鈕可以看到,不使用緩存和使用緩存 FPS 值差距還是挺大的,我電腦在使用緩存的時(shí)候基本在 60fps,不使用會(huì)降到 15fps 左右。大家可以打開控制臺(tái)或者在 這里 查看代碼。
下面列出主要的代碼片段:

class Ball {
  constructor(x, y, vx, vy, useCache = true) {
    // ...
    if (useCache) {
      this.useCache = useCache;
      this.cacheCanvas = document.createElement("canvas");
      // 離屏 canvas 寬高取要渲染圖形的寬高,不可以取真實(shí) canvas 的寬高,否則會(huì)渲染大量無用區(qū)域
      this.cacheCanvas.width = 2 * (this.r + BORDER_WIDTH);
      this.cacheCanvas.height = 2 * (this.r + BORDER_WIDTH);
      this.cacheCtx = this.cacheCanvas.getContext("2d");
      this.cache();
    }
  }

  paint() {
    // 使用緩存直接使用創(chuàng)建的離屏canvas,否則直接繪制圖形
    if (!this.useCache) {
      ctx.save();
      ctx.lineWidth = BORDER_WIDTH;
      ctx.beginPath();
      ctx.strokeStyle = this.color;
      ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
      ctx.stroke();
      ctx.restore();
    } else {
      ctx.drawImage(
        this.cacheCanvas,
        this.x - this.r,
        this.y - this.r,
        this.cacheCanvas.width,
        this.cacheCanvas.height
      );
    }
  }

  move() {
    // ...
  }

  cache() {
    // 繪制圖形
    this.cacheCtx.save();
    this.cacheCtx.lineWidth = BORDER_WIDTH;
    this.cacheCtx.beginPath();
    this.cacheCtx.strokeStyle = this.color;
    this.cacheCtx.arc(
      this.r + BORDER_WIDTH,
      this.r + BORDER_WIDTH,
      this.r,
      0,
      2 * Math.PI
    );
    this.cacheCtx.stroke();
    this.cacheCtx.restore();
  }
}

解釋一下二者區(qū)別:

使用緩存:在實(shí)例化每個(gè)圖形的時(shí)候(渲染之前),先將圖形渲染到一個(gè)離屏的 canvas 上,在渲染的時(shí)候,直接用 drawImage 將離屏的 canvas 渲染。

不使用緩存: 在渲染的時(shí)候直接繪制圖形

使用緩存的時(shí)候,有一點(diǎn)需要注意的是要控制好離屏 canvas 的大小,不可以直接取和渲染 canvas 的實(shí)際寬高,否則會(huì)渲染很多無用的空間,比如上面例子中每個(gè)離屏 canvas 的寬高只需要和對(duì)應(yīng)圖形的寬高一致。

this.cacheCanvas.width = 2 * (this.r + BORDER_WIDTH);
this.cacheCanvas.height = 2 * (this.r + BORDER_WIDTH);

上述代碼中主要節(jié)省時(shí)間的地方在 paint 函數(shù)中使用 drawImage會(huì)比直接繪制圖形節(jié)省時(shí)間,那么是否所有場(chǎng)景都是這樣呢?我們?cè)賮砜聪旅孢@個(gè) 例子.

這個(gè)例子和上面的只有繪制圖形的代碼不同:

// 從復(fù)雜圖形變成了簡(jiǎn)單圖形
cache() {
  this.cacheCtx.save();
  this.cacheCtx.lineWidth = BORDER_WIDTH;
  this.cacheCtx.beginPath();
  this.cacheCtx.strokeStyle = this.color;
  this.cacheCtx.arc(
    this.r + BORDER_WIDTH,
    this.r + BORDER_WIDTH,
    this.r,
    0,
    2 * Math.PI
  );
  this.cacheCtx.stroke();
  this.cacheCtx.restore();
}

只是cache方法中把復(fù)雜圖形變成了簡(jiǎn)單的圖形。但實(shí)際效果相差甚遠(yuǎn),使用緩存和不使用性能差距并不大,甚至不使用時(shí) fps 值還更高一些。

所以看來圖形的復(fù)雜度,直接會(huì)影響 canvas 緩存的效果,我們?cè)陂_發(fā)過程中,也不能盲目引入緩存,要權(quán)衡利弊。fabric 中緩存是默認(rèn)開啟的,同時(shí)也可以設(shè)置 objectCaching 為 false 禁用。

lower-canvas 和 upper-canvas

如果大家細(xì)心的話應(yīng)該會(huì)發(fā)現(xiàn),當(dāng)我們執(zhí)行new fabric.Canvas("domeId")的時(shí)候,在頁面上 dom 元素就改變了,fabric 復(fù)制了一層 canvas 蓋在了我們定義的 canvas 上面:

fabric 這樣設(shè)計(jì)將渲染層和交互層做了分離,lower-canvas 只負(fù)責(zé)渲染元素;所有的交互,比如框選,事件處理都在 upper-canvas 上。

順便提一下,fabric 提供了渲染靜態(tài)畫布的方法,如果你的畫布不需要任何交互,只用來展示,那么可以用new fabric.StaticCanvas("domId", options)來初始化,這時(shí)候 dom 結(jié)構(gòu)中就只有一個(gè) canvas,沒有 upper-canvas 了。

說到這里,很多同學(xué)可能會(huì)想到,事件是怎樣綁定的呢?其實(shí)兩個(gè) canvas 大小等屬性都是一致的,所以坐標(biāo)也是可以對(duì)應(yīng)上的,比如在 upper-canvas 上某個(gè)位置點(diǎn)擊了一下,那么就可以去 lower-canvas 上就可以用這個(gè)坐標(biāo)去找是否點(diǎn)擊到了一個(gè)元素,那么問題來了,如何判斷一個(gè)點(diǎn)在一個(gè)圖形中呢?

如何判斷點(diǎn)在圖形中

這個(gè)問題網(wǎng)上有個(gè)比較普遍的方案,就是通過畫一條射線,通過交點(diǎn)奇偶性來判斷。如下圖:

設(shè)目標(biāo)點(diǎn) P,使 P 點(diǎn)向任意一個(gè)方向畫一條射線,保證不與圖形的頂點(diǎn)相交;

記錄射線與圖形的交點(diǎn)數(shù)量 n;

n 為奇數(shù)時(shí),P 就在圖形內(nèi),反之則在圖形外。

而 fabric 中并沒有用這種方法,原因很簡(jiǎn)單,這個(gè)算法是有前提的:發(fā)出的射線不能與圖形任何頂點(diǎn)相交。 這個(gè)前提對(duì)于我們主觀來判斷是很簡(jiǎn)單的,但程序中處理可能就需要大量的代碼去判斷是否與交點(diǎn)相交,如果相交再重新生成一條射線。

fabric 中使用的算法對(duì)上述算法進(jìn)行了改進(jìn),我們結(jié)合下圖來解釋:

其中 e1 ~ e5 分別為多邊形的邊,P 為目標(biāo)點(diǎn),黑色實(shí)心點(diǎn)為多邊形的頂點(diǎn),r 為 P 延 X 軸發(fā)出的射線(不同于上面的方法,這里我們約定 r 射線只能延 X 軸發(fā)出)。

設(shè)目標(biāo)點(diǎn) P,使 P 延 X 軸方向畫一條射線( y=Py ),設(shè) intersectionCount = 0

遍歷多邊形的所有邊,設(shè)邊的頂點(diǎn)為 p1, p2

如果 p1y < Py,而且 p2y < Py,跳過(也就是這條邊在 P 點(diǎn)下方)

如果 p1y >= Py,而且 p2y >= Py,跳過(也就是這條邊在 P 點(diǎn)上方)

否則,設(shè)射線與這條邊的交點(diǎn)為 S,如果 Sx >= Px,intersectionCount加 1

最終如果intersectionCount為奇數(shù),則在圖形內(nèi),反之則在圖形外。

判斷的部分用代碼實(shí)現(xiàn)類似:

// point 目標(biāo)點(diǎn),lines多邊形的所有邊
function checkPoint(point, lines) {
  let intersectionCount = 0;
  let { x, y } = point;
  for (let i = 0; i < lines.length; i++) {
    let line = lines[i];
    // 兩個(gè)頂點(diǎn)
    let { p1, p2 } = line;
    if ((p1.y < y && p2.y < y) || (p1.y >= y && p2.y >= y)) {
      continue;
    } else {
      const sx = ((y - p1.y) / (p2.y - p1.y)) * (p2.x - p1.x) + p1.x;
      if (sx >= x) {
        intersectionCount++;
      }
    }
  }
  return intersectionCount % 2 === 0;
}

這里是個(gè)簡(jiǎn)單的例子。同時(shí) 這里 可以獲取完整代碼。

處理 Retina 屏

Retina 屏幕模糊的問題,直接給出處理方法,就不展開說了。

canvas.width, canvas.height 放大至 dpi 倍

canvas.style.width, canvas.style.height 設(shè)為原始 canvas 寬高

ctx 縮放 dpi 倍

代碼:

function initRetina(canvas, ctx) {
  const dpi = window.devicePixelRatio;
  canvas.style.width = canvas.width + "px";
  canvas.style.height = canvas.height + "px";
  canvas.setAttribute("width", canvas.width * dpi);
  canvas.setAttribute("height", canvas.height * dpi);
  ctx.scale(dpi, dpi);
}

查看例子,完整代碼

小結(jié)

本篇文章主要針對(duì)fabric.canvas模塊,介紹了相關(guān) canvas 緩存,fabric 中判斷點(diǎn)在圖形中的算法以及如何處理 retina 屏幕的知識(shí),作為系列的第一篇文章,可能會(huì)有很多問題,如有錯(cuò)誤及意見,歡迎批評(píng)指正。

參考文獻(xiàn):  
http://idav.ucdavis.edu/~okre...
http://www.geog.ubc.ca/course...
https://www.cnblogs.com/axes/...
http://fabricjs.com/docs/

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/104044.html

相關(guān)文章

  • 我從HTML的meta中學(xué)到了什么

    摘要:用于設(shè)置縮放比例,可以是任意數(shù)值的比例。禁止,不禁止是否就職瀏覽器識(shí)別頁面中的郵件地址。具體可參考如下代碼以前,我不知道或中添加有什么用,但上面的例子是它的一個(gè)用途。其他的使用可參考或模擬原生效果。更多的前端相關(guān)資源,可關(guān)注我的 meta meta中有這樣幾個(gè)常用屬性:http-equiv,name,content,包括html5新增的charset。 注意:content屬性用來存儲(chǔ)...

    rozbo 評(píng)論0 收藏0
  • 「讀懂源碼系列2」我從 lodash 源碼中學(xué)到的幾個(gè)知識(shí)點(diǎn)

    摘要:今天要講的,是我從的源碼實(shí)現(xiàn)文件中學(xué)到的幾個(gè)很基礎(chǔ),卻又容易被忽略的知識(shí)點(diǎn)。在函數(shù)式編程中,函數(shù)是一等公民,它可以只是根據(jù)參數(shù),做簡(jiǎn)單的組合操作,再作為別的函數(shù)的返回值。所以,閱讀源碼,是一種很棒的重溫基礎(chǔ)知識(shí)的方式。 showImg(https://segmentfault.com/img/bVbpTSY?w=750&h=422); 前言 上一篇文章 「前端面試題系列8」數(shù)組去重(1...

    Amio 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<