教你作 2D Canvas 渲染優化(螞蟻金服可視化 AntV 實踐)

簡介

HTML 上的圖形渲染主要有兩種方案 SVG 和 Canvas,前者更易於使用,然後者潛力更大,本文主要關注如何使用 Canvas 繪製出更多的圖形,提供更加流暢的交互。本文的內容有:javascript

  • 渲染機制
  • 性能瓶頸
  • 繪製更多的圖形
  • 讓交互更流暢
  • webGL 實現 2D 渲染

渲染機制

咱們以簡單的一個圓爲示例,來對比 SVG 和 Canvas 的渲染:
  html

image.png

  • SVG 同其餘的 HTML 標籤同樣,每一個圖形對應一個標籤,圖形的繪製同 HTML 標籤一致
<svg>
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/> </svg>
複製代碼
  • 而 Canvas 本質上是一個圖片,不管有多少個圖形只有一個標籤,須要使用 javascript 來繪製圖形
<canvas></canvas>
<script>
  var ctx=c.getContext("2d");
	ctx.strokeStyle = 'black';
	ctx.fillStyle = 'red'
	ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.arc(100,50,40,0,2 * Math.PI);
  ctx.stroke();
</script>
複製代碼

PS:你能夠把 SVG 的理解成製做完一個個的圖形放到頁面上,而 Canvas 則是使用畫筆一個個的繪製圖形。
這篇文章並非對比 SVG 和 Canvas 差別的文章,二者的差異 w3cshool 的描述很是準確。html5

如何拾取圖形

circle-hit.gif

因爲 SVG 的圖形是一個個的 HTML 標籤,因此 SVG 的圖形自然支持瀏覽器的全部事件。而 Canvas 是一張畫布,使用一支筆將圖形繪製到畫布上後,這些圖形就成爲了畫布的一部分,每一個圖形都沒法獨立的對瀏覽器的事件進行相應。有什麼方式判斷指定點所在的圖形呢?主要有兩種方案:

  1. 瀏覽器提供了 isPointInPath 和 isPointInStroke 兩個方法斷定點是否在圖形內,點是否在圖形的邊上。
  2. 緩存全部圖形的屬性,使用數學方法來判斷指定點所在的圖形。

咱們依然以一個圓爲示例,來看這兩種方式: java

瀏覽器的方法

使用瀏覽器的方法須要從新繪製一遍圖形:react

ctx.beginPath();
 ctx.arc(100,50,40,0,2 * Math.PI);
 const inPath = ctx.isPointInPath(100, 100);
 const inStroke = ctx.isPointInStroke(100, 100);
複製代碼
  • 這是一種使用簡單,同時全部圖形都適用的方式,可是成本巨大,例如:鼠標每在畫布上移動一次,都會致使全部的圖形繪製一遍。
  • 建議圖形個數小於 500 個時使用這個方案。

數學拾取

須要對每種圖形提供判斷是否在圖形內部和圖形邊上的方法:git

function isInCircle(point, x, y, r) {
	return distance(point.x, point.y, x, y) <= r;
}

function isInCircleStroke(point, x, y, r, lineWidth) {
  const d = distance(point.x, point.y, x, y);
	return d <= r + lineWidth / 2 && d >= r - lineWidth / 2;
}
const point = {x: 100, y: 100};
const inPath = isInCircle(point, 100, 50, 40);
const inStroke = isInCircle(point, 100, 50, 40, 2);
複製代碼
  • 性能好:從性能上來講數學拾取的性能比使用瀏覽器的方法要快 20 倍左右
  • 實現複雜:從實現上來講須要實現全部幾何圖形的數學計算,更多的數學計算參考 2D 圖形計算

PS:二者的性能測試對比github

瀏覽器 API 數學計算
1 111.02999997092411 4.779999959282577
2 110.53000000538304 5.694999999832362
3 117.55500000435859 7.979999994859099
4 126.2599999899976 5.354999972041696
5 110.8949999907054 4.725000006146729
6 121.6549999662675 6.2049999833106995
7 121.18500005453825 4.529999976512045
8 116.78500002017245 8.094999997410923
9 124.06000000191852 8.925000031013042
10 124.42499998724088 4.849999968428165
平均值 118.43799999915063 6.113999988883734

更多更快的拾取方案能夠參考 2D 圖形拾取方案web

圖形的更新

對於 SVG 的圖形來講,直接修改對應標籤的屬性便可,有瀏覽器控制刷新圖形。可是對於 Canvas 來講須要清除整個畫布,從新繪製全部的圖形,也就是說 Canvas 畫布上有 10W 個圖形,僅僅更新一個圖形時,其餘 99999 個圖形也須要從新繪製。算法

function drawAll() { 
	// 繪製全部圖形
}
function repaint() {
	ctx.clearRect(0, 0, width, height);
  drawAll();
}
複製代碼

性能瓶頸

從上面的渲染機制咱們能夠天然的推導出 Canvas 的圖形渲染的性能瓶頸主要在三方面:canvas

  • 同一時間繪製過多的圖形,會阻塞瀏覽器的進程,致使頁面不能響應
  • 鼠標在畫布上移動時,若是不能及時捕捉鼠標,會致使卡頓
  • 圖形更新時,重繪的時間過長,則幀率很是低

渲染的成本

咱們以繪製 1W 個圓做爲示例,來看一下單次繪製的成本:

image.png

須要 51 ms,假如咱們要繪製 10W 個點,則須要 510ms

拾取的成本

前面咱們測試過圖形拾取和數學拾取的差別,1W 個圓的拾取須要 11ms左右,若是再加上圖形刷新的響應,能夠預期幀率會很是低。

更新的成本

咱們以鼠標在畫布上移動,移入一個圓這個圓變顏色,咱們來看一下畫布總體刷新時的效果:

circle-hit1.gif

  • 能夠看到明顯的延遲,鼠標移動開一段距離後,點才響應
  • 鼠標移動過的路徑,大部分圓沒有響應

若是咱們對圓進行動畫看一下幀率:

circle-hit2.gif

能夠看到動畫的幀率在 8 幀左右

繪製更多的圖形

首次渲染時的優化

當一次渲染的圖形過多時,將一次渲染分紅屢次渲染,每次渲染時間增長几毫秒的間隔,這時候就不會卡頓:

image.png

這種方案雖然會增長總的渲染時長,可是能夠下降頁面的卡頓感,對全部圖形進行總體更新時也可使用這個方案,可是進行交互時這種方案會帶來必定的延遲。
draw2.gif

  • 這是一個 10W 個點渲染(局部 畫布 1000 * 1000,這裏僅顯示了500* 500 的範圍)的效果,能夠保證近乎 60幀的效果
  • 分段渲染的核心在於中間空白的間隔要足夠小,這裏面有不少的算法,就不在這裏展開
  • 分段渲染時數據更新時怎麼處理,能夠參考 react fiber 的實現。

頻繁渲染的優化

鼠標在畫布上移動時,不斷的致使重繪,咱們只要可以保證 60 幀的重繪頻率便可,因此重繪的間隔不能小於 16ms,咱們能夠將持續渲染的同步機制,改爲每 16 ms 渲染的異步延遲渲染機制,這樣能夠大大下降重繪的頻率。

image.png

異步渲染依然有不少須要思考的地方,能夠參考 延遲渲染實現 

更新的優化

咱們能夠實現圖形的局部刷新,在局部刷新時僅清空圖形所在的包圍盒,全部與這個包圍盒相交的圖形所有刷新,這時候咱們來看上面的兩個示例:當鼠標在畫布上移動時,這就流暢多了,基本上沒有延遲

circle-hit4.gif

一樣的動畫,能夠看到支持了局部刷新後能夠直接到達 60 幀
circle-hit3.gif

局部刷新的具體實現比這複雜的多,後面我會寫一篇更詳細的關於局部刷新的文章,這裏能夠參考  局部刷新文檔 

拾取的優化

拾取的優化咱們在上面已經進行了簡單的說明,數學拾取的性能遠遠超過使用瀏覽器的方法來拾取,更多的拾取方案參考:2D 圖形拾取方案

webGL 實現 2D 渲染

渲染性能的提高

因爲 webGL 的渲染是在 GPU 中進行,能夠顯著的提高渲染效率,能夠看下面的示例:

draw3.gif

  • 這是一個 80 * 80 * 80 = 512000 個點的示例

一些限制

  • 因爲 webGL 的渲染時基於光柵(點),因此繪製線時本質上是經過一個個的點來繪製,逐點計算貝塞爾等曲線不現實,所以繪製的這些曲線不夠平滑。
  • 繪製圖形時儘可能不要直接在 cpu 中計算各個圖形的幾何模型,而使用 shader 對圖形進行計算和渲染,不然性能反而會降低
  • 文本的渲染很是複雜,也不會帶來性能的提高

總結

這些優化大部分已經在 2D 繪圖引擎 G上實現 ,2D 圖形的渲染優化主要在異步渲染、拾取加速和局部渲染三個方面,可是每一個方面都很是複雜,均可以獨立成章,本文僅僅是從思路上進行講解,更多更細的分析都會在後面提供獨立的章節進行講解,敬請期待!

AntV 官網:antv.vision/
2D 繪圖引擎 G:github.com/antvis/g

相關文章
相關標籤/搜索