欧美人与禽2O2O性论交,秋霞免费视频,国产美女视频免费观看网址,国产成人亚洲综合网色欲网

小程序和H5中canvas卡頓的性能優(yōu)化方向和實踐(小程序canvas使用)

什么是canvas? 首先介紹下canvas, 前端的同學(xué)可能很熟悉,舉個很簡單的例子,
平常用的網(wǎng)頁截圖、H5游戲、前端動效、可視化圖表…,都有canvas 的應(yīng)用場景, 官方的定義:
canvas是HTML5提供的一種新標(biāo)簽,
ie9才開始支持的,canvas是一個矩形區(qū)域的畫布,可以用JS控制每一個像素在上面繪畫。canvas 標(biāo)簽使用 JavaScript
在網(wǎng)頁上繪制圖像,本身不具備繪圖功能。canvas 擁有多種繪制路徑、矩形、圓形、字符以及添加圖像的方法。
看著很簡單,其實canvas這個標(biāo)簽的加入,賦予了我們更多創(chuàng)建驚艷的前端效果的能力。但是你知道他也有性能問題??本篇文章就簡單談一談Canvas的性能優(yōu)化。

哪些因素會影響canvas的性能

canvas優(yōu)化的幾種方式

我們都知道瀏覽器上渲染動畫 每一秒高達(dá)60幀,也就是1秒鐘內(nèi)我們完成60次圖像繪制, 也就是每一幀圖像的繪制時間其實就是(1000/ 60)。 如果在每一幀動畫的時間小于 16.7 ms 辣么就會出現(xiàn)卡頓、丟幀。而canvas 其實是一個指令式繪圖系統(tǒng), 他通過繪圖指令來完成繪圖操作。
影響canvas兩個很關(guān)鍵的因素:
第一個渲染的圖形數(shù)量多,就是調(diào)用繪圖指令的次數(shù)比較多,
第二個渲染的圖形大,就是一次繪圖渲染的時間比較長

優(yōu)化canvas

1. 減少繪圖指令的調(diào)用

這句話怎么理解呢 , 假設(shè)你要在場景中畫正n變形,這是一個 很常見的需求可能你稍不注意寫下了下面這幾行代碼:

function drawAnyShape(points) { for(let i=0; i<points.length; i ) { const p1 = points[i] const p2 = i=== points.length - 1 ? points[0] : points[i 1] ctx.fillStyle = 'black' ctx.beginPath(); ctx.moveTo(...p1) ctx.lineTo(...p2) ctx.closePath(); ctx.stroke() } }


points 對應(yīng)的生成多邊形的點,代碼如下:

function generatePolygon(x,y,r, edges = 3) { const points = [] const detla = 2* Math.PI / edges; for(let i= 0;i<edges;i ) { const theta = i * detla; points.push([x r * Math.sin(theta), y r * Math.cos(theta)]) } return points }

?
一看這fps低成這個樣子,很多人這時候說,你畫的圖形多,那我只要悄悄的改下代碼,就能讓fps 回歸正常

重寫了正多邊形的方法:

function drawAnyShape2(points) { ctx.beginPath(); ctx.moveTo(...points[0]); ctx.fillStyle = 'black' for(let i=1; i<points.length; i ) { ctx.lineTo(...points[i]) } ctx.closePath(); ctx.stroke() }

看了下fps 已經(jīng)成功升到了30fps, 這是為什么呢, 第一段我們在循環(huán)中去做繪圖操作, 循環(huán)一次, stoke() 一次,這顯然是不合理的,第二個直接把stoke() ,放到循環(huán)外,其實就調(diào)用了一次,所以我們可以得出減少繪圖指令是可以提高canvas的性能的

2.分層渲染

為什么需要分層渲染, 在游戲中,假設(shè)人物的不停地在移動,但是呢背景可能加了很多花里呼哨的元素,但是我在每一次更新的時候,場景本身是不變的,變的只有人物不停的移動,如果每一幀再去重繪不就造成了性能浪費(fèi), 這時候分層canvas就出現(xiàn)了 我們先看下一張圖你可能就明白了。

我通過3個canvas疊在一起,通過設(shè)置每個canvas的 z-index 達(dá)到了3個畫布還是在同一層的錯覺,這樣我在requestAnimation中,只需要對 動的圖形去做重新繪制就好了,其余的依舊是保持不動 。

偽代碼

<canvas id="backgroundCanvas" /><canvas id="peopleActionCanvas" />const peopleActionCanvas = document.getElementById('peopleActionCanvas');const backgroundCanvas = document.getElementById('backgroundCanvas');?function draw(){ drawPeopleAction(peopleActionCanvas); if (needDrawBackground) { drawBackground(backgroundCanvas); } requestAnimationFrame(draw);}

一個背景層一個運(yùn)動層, 在抽象一點,我們什么時候應(yīng)該去做分層 ,如果畫布純是靜態(tài)的就沒有必要去做分層了, 如果當(dāng)前有靜態(tài)有東動態(tài)的,你可以邏輯層放在最上面,然后展示層 放在最底下就可以實現(xiàn)所謂的 分層渲染了,但是最好保持在3-5個。

3. 局部渲染

局部渲染的話其實就是調(diào)用canvas 的 clip方法。官方文檔MDN 對這個方法的使用

CanvasRenderingContext2D.clip() 是 Canvas 2D API 將當(dāng)前創(chuàng)建的路徑設(shè)置為當(dāng)前剪切路徑的方法

如何用canvas 畫一個1/4圓。

const canvas = document.getElementById('canvas');const ctx = canvas.getContext('2d');ctx.fillStyle = 'red'ctx.arc(100, 100, 75, 0, Math.PI*2, false);//ctx.clip();ctx.fillRect(0, 0, 100,100);

這里填充的時候 沒有用clip 畫面上應(yīng)該是一個矩形。

這時候我把clip注釋解開來, 矩形變成了一個半圓。 所以clip 這個 api 結(jié)合 fillRect 填充 就是實現(xiàn)填充任意圖形路徑。

canvas 中畫了1000 個圓形, 如果你只改一個顏色,那其他999都是不變的 這種浪費(fèi)是肯定存在性能問題, 如果在做動畫效果可想而知,丟幀非常厲害。 這里就可以使用我們上面的api

正確的做法其實就是我們要做局部刷新:

確定改變的元素的包圍盒(是否存在相交)
畫出路徑 然后 clip
最后重新繪制繪制改變的圖形
clip() 確定繪制的的裁剪區(qū)域,區(qū)域之外的圖形不能繪制,詳情查看 CanvasRenderingContext2D.clip() clearRect(x, y, width, height) 擦除指定矩形內(nèi)的顏色,查看 CanvasRenderingContext2D.clearRect()

包圍盒
用一個框去把圖形包圍住, 其實在幾何中我們叫包圍盒 或者是boundingBox。 可以用來快速檢測兩個圖形是否相交, 但是還是不夠準(zhǔn)確。最好還是用圖形算法去解決。 或者游戲中的碰撞檢測,都有這個概念。這里討論的是2d的boudingbox, 還是比較簡單的。

虛線框其實就是boundingBox, 其實就是根據(jù)圖形的大小,算出一個矩形邊框。理論我們知道了,映射到代碼層次, 我們怎么去表達(dá)呢? 這里帶大家原生實現(xiàn)一下bound2d 類, 其實每個2d圖形,都可以去實現(xiàn)。 因為2d圖形都是由點組成的,所以只要獲得每一個圖形的離散點集合, 然后對這些點,去獲得一個2d空間的boundBox。

4.離屏CANVAS 和WEBWORKER

我們先說下 什么是離屏canvas???

OffscreenCanvas提供了一個可以脫離屏幕渲染的canvas對象。它在窗口環(huán)境和web worker環(huán)境均有效。

脫離屏幕渲染的canvas對象,這對我們實際寫動畫的時候真的有用嗎???

想象以下這個場景:如果發(fā)現(xiàn)自己在每個動畫幀上重復(fù)了一些相同的繪制操作,請考慮將其分流到屏幕外的畫布上。 然后,您可以根據(jù)需要頻繁地將屏幕外圖像渲染到主畫布上,而不必首先重復(fù)生成該圖像的步驟。由于瀏覽器是單線程,canvas的計算和渲染其實是在同一個線程的。這就會導(dǎo)致在動畫中(有時候很耗時)的計算操作將會導(dǎo)致App卡頓,降低用戶體驗。

幸運(yùn)的是, OffscreenCanvas 離屏Canvas可以非常棒的解決這個麻煩!

到目前為止,canvas的繪制功能都與標(biāo)簽綁定在一起,這意味著canvas API和DOM是耦合的。而OffscreenCanvas,正如它的名字一樣,通過將Canvas移出屏幕來解耦了DOM和canvas API。

由于這種解耦,OffscreenCanvas的渲染與DOM完全分離了開來,并且比普通canvas速度提升了一些,而這只是因為兩者(Canvas和DOM)之間沒有同步。但更重要的是,將兩者分離后,canvas將可以在Web Worker中使用,即使在Web Worker中沒有DOM。這給canvas提供了更多的可能性。

這就離屏canvas 為啥和webworker 這么配的緣故了。

如何創(chuàng)建離屏CANVAS?
創(chuàng)建離屏canvas有兩種方式:

一種是通過OffscreenCanvas的構(gòu)造函數(shù)直接創(chuàng)建。比如下面的示例代碼:

// 離屏canvas const offscreen = new OffscreenCanvas(200, 200);第二種是使用canvas的transferControlToOffscreen函數(shù)獲取一個OffscreenCanvas對象,繪制該OffscreenCanvas對象,同時會繪制canvas對象。比如如下代碼: const canvas = document.getElementById('canvas');const offscreen = canvas.transferControlToOffscreen();我寫了下面這個小demo 驗證下到底是不是可靠的 const canvas = document.getElementById('canvas'); // 離屏canvas const offscreen1 = new OffscreenCanvas(200, 200); const offscreen2 = canvas.transferControlToOffscreen(); console.error(offscreen1,offscreen2, '222')

離屏canvas怎么與主線程的canvas通信呢?

這時候引用另外一個api transferToImageBitmap

通過transferToImageBitmap函數(shù)可以從OffscreenCanvas對象的繪制內(nèi)容創(chuàng)建一個ImageBitmap對象。該對象可以用于到其他canvas的繪制。

比如一個常見的使用是,把一個比較耗費(fèi)時間的繪制放到web worker下的OffscreenCanvas對象上進(jìn)行,繪制完成后,創(chuàng)建一個ImageBitmap對象,并把該對象傳遞給頁面端,在頁面端繪制ImageBitmap對象。

寫個小demo測試下:

優(yōu)化前
我們畫 10000 * 10000 個矩形看看頁面的響應(yīng)和時間,代碼如下:

const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); function draw() { for(let i = 0;i < 10000;i ){ for(let j = 0;j < 1000;j ){ ctx.fillRect(i*3,j*3,2,2); } } } draw() ctx.arc(100,75,50,0,2*Math.PI); ctx.stroke()

可以很明顯的感受到,在渲染出圖形前,瀏覽器是失去響應(yīng)的,我們無法做認(rèn)可操作。這樣的用戶體驗肯定是非常差的。

優(yōu)化后
我們使用離屏canvas webworker 進(jìn)行優(yōu)化,代碼如下:

我們先看下worker 的代碼:

let offscreen,ctx;// 監(jiān)聽主線程發(fā)的信息onmessage = function (e) { if(e.data.msg == 'init'){ init(); draw(); }} function init() { offscreen = new OffscreenCanvas(512, 512); ctx = offscreen.getContext("2d");}// 繪制圖形function draw() { ctx.clearRect(0,0,offscreen.width,offscreen.height); for(var i = 0;i < 10000;i ){ for(var j = 0;j < 1000;j ){ ctx.fillRect(i*3,j*3,2,2); } } const imageBitmap = offscreen.transferToImageBitmap(); // 傳送給主線程 postMessage({imageBitmap:imageBitmap},[imageBitmap]);}

看下主線程的代碼:

const worker = new Worker('./worker.js')worker.postMessage({msg:'init'});worker.onmessage = function (e) { // 這里就接受到work 傳來的離屏canvas位圖 ctx.drawImage(e.data.imageBitmap,0,0);} ctx.arc(100,75,50,0,2*Math.PI); ctx.stroke()

對比兩個很明顯的變化, 畫多個矩形是個非常耗時的操作會影響其他圖形渲染,可以采用離屏canvas webworker 來解決這種失去響應(yīng)。

5.禁用頁面和canvas的滾動事件

touchmove事件和滾動事件有時候是有沖突的,這樣在我們移動手指時回導(dǎo)致繪畫效果的卡頓,或者事件點位跳躍的情況發(fā)生,這時候我們只需要把滾動事件禁用既可以了
禁用方式是在標(biāo)簽上加上或者微信小程序頁面加上"disableScroll": true,如果是uniapp在pages.json加上
“disableScroll”: true

<canvas :id="cid" disable-scroll="true" type="2d" ></canvas>

小程序和H5中canvas卡頓的性能優(yōu)化方向和實踐(小程序canvas使用)小程序和H5中canvas卡頓的性能優(yōu)化方向和實踐(小程序canvas使用)

總結(jié)

  1. 繪制的圖形的數(shù)量和大小會影響canvas的性能,減少繪圖次數(shù),減少canvas接口調(diào)用次數(shù)
  2. 圖形數(shù)量過多,但是只刷新部分 可以使用局部渲染
  3. 邏輯層和背景圖層分離 可以使用分層渲染
  4. 某些長時間的邏輯影響主線程的, 可以使用離屏渲染 和webworker 來解決問題
  5. 禁用頁面和容器的滾動

相關(guān)新聞

聯(lián)系我們
聯(lián)系我們
公眾號
公眾號
在線咨詢
分享本頁
返回頂部