前端“油畫設計師”——雙快取繪製與油畫分層機制

葡萄城技術團隊發表於2021-09-01

背景

Canvas在影像處理、繪製渲染上有一些得天獨厚的優勢。但是當我們當前展示的內容中在主題內容變化不大的情況下,會有一些小部分內容的變化,在頁面重新整理或者滾動的時候,一幀中會有很多複雜內容元素的圖畫運算,重新對頁面元素繪製會導致CPU使用率飆升。

而重新繪製的過程,實質上是一個不斷刮白-重畫的過程。但在螢幕上完成這一系列操作是需要一定時間的,而且螢幕上的圖形越複雜,所花的時間就越長,我們肉眼可見的刮白-重畫操作,在使用過程中就會讓就會直接感覺到螢幕的閃爍。

重繪帶來的效能負擔和閃爍的問題,會給使用者帶來較差的使用體驗。為了更好的優化這個兩個問題,出現了雙快取畫布和油畫分層的繪製方法。而本節內容我們也將從電子表格技術出發,為大家揭祕在電子表格技術中雙快取與優化技術的具體應用。

雙快取畫布

現在我們有一幅圖需要放在Canvas中,使用drawImage()方法,有三種寫法:

// 將image放到目標canvas指定位置

void ctx.drawImage(image, dx, dy); 

// 將image放到目標canvas指定位置,指定寬高渲染

void ctx.drawImage(image, dx, dy, dWidth, dHeight);

// 將image裁剪之後放到目標canvas指定位置,指定寬高渲染

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

第一種方法只是把圖片原樣放到Canvas中,第二種方法指定寬高就意味著放大或者縮小圖片後再放進去,第三種是將圖片裁剪後再放大或者縮小放到canvas中,這三種寫法操複雜度作依次增加,效能開銷也隨之增大。

而如果使用離屏渲染(即我們所說的雙快取畫布),我們可以預先把圖片裁剪成想要的尺寸,然後將該內容儲存起來,繪製的時候直接使用第一種寫法直接將圖片放入Canvas中。

// 在離屏 canvas 上繪製

var offscreencanvas = document.createElement('canvas');

// 寬高賦值為想要的圖片尺寸

offscreencanvas.width = dWidth;

offscreencanvas.height = dHeight;

// 裁剪

offscreencanvas.getContext('2d').drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

// 在檢視canvas中繪製

viewcontext.drawImage(canvas, x, y);

雙快取畫布技術的核心在於系統需要在記憶體中開闢一塊與當前畫面等大的“邏輯螢幕“。我們的畫圖和動畫操作都會先作用於這塊”邏輯螢幕“中,當一個操作在這塊”邏輯螢幕“上完成之後,再把整塊”邏輯螢幕“投放到我們的螢幕上。

(離屏渲染原理示意圖)

在這樣的過程之下,我們是無法看到整個圖形在螢幕上的重繪過程,從而解決了閃爍問題。就好像看動漫一樣,不用雙快取技術,就是畫一幀看一幀,肯定會卡頓。而用了雙快取技術,會事先把每一幀畫好,不斷翻動展示出來。

(逐幀動畫)
Canvas為此提供了OffscreenCanvas方法,用來構建一個可以脫離螢幕渲染的canvas物件,它在視窗環境和web worker環境均有效。對於一些渲染,如果建立 Image 再進行渲染,會消耗大量 CPU,但用離屏渲染,實測在高頻事件中 CPU 使用率減少了一倍之多。

油畫分層繪製

分層渲染來處理畫面動畫的思路並不是現在才有的,從非物質文化遺產皮影戲、套色印刷技術,到現在的音影工業等眾多領域都有頻繁出現, 而這種思想在Canvas中也處在基石的地位。

(分層渲染原理示意圖)

Canvas分層的思想是,動畫中每種元素,對渲染和動畫的要求是不一樣的。

用下圖舉個例子,在這張圖片中除了貓本身在運動外,背景以及下方的文字都是靜止重複的。

(油畫分層機制示意圖)

按照分層的邏輯,我們需要頻繁更新繪製的只有最上方的貓咪。這個方法類似油畫的繪製,所以也被稱為油畫分層機制。使用這個方法結合雙緩衝技術可以有效的將重複繪製的內容分流到螢幕外的畫布上,然後再根據我們的需求將螢幕外影像渲染到主畫布上,省去了頻繁生成重複部分的步驟。

技術應用落地

在實際應用中需要在前端對複雜內容進行渲染或者處理大量資料時,為了更好地對效能進行優化,現在已經有很多專案實際採用了Canvas的雙快取畫布和油畫分層技術。我們在做電子表格技術選型時也考慮到了這些問題,在電子表格應用專案中,我們動輒需要處理百萬數量級資料內容,這種情況下瀏覽器對錶格內容渲染和資料處理的效能就顯得無比重要。

上圖是純前端電子表格中50000*20=100000個資料,處理只需要0.038s。在該純前端電子表格中,整個繪製引擎根據油畫繪製原理,分為主體圖層和裝飾圖層,主題圖層將會渲染持久的,不會輕易改變的元素,例如背景,單元格,表格線等。而裝飾圖層則會渲染常變性元素,例如選擇框,拖拽框,懸浮效果等。在下圖中第一層到第四層都是主體圖層的內容,第五層是裝飾圖層。

除此之外整個的繪製過程並不是從資料層(Model)直接到檢視層(View)的。而是根據表格內容的特殊性,實現了根據檢視層形狀,從資料層組合出一層專屬檢視層的檢視資料(ViewModel),再配合前文提到的雙快取畫布繪製機制,完成整個表格按需繪製的需求,並快取繪製結果,進一步提升繪製效能。

主體圖層不是直接繪製在使用者能看到的主畫布上,而是繪製在一個看不見的快取畫布上。在需要渲染時,只需要講快取畫布的內容克隆到主畫布上,再附加上裝飾圖層元素

這樣,當表格需要更新時候,比如單元格背景改變,只需要在克隆快取畫布後重繪對應單元格內容即可。

而當表格向下滾動時,表格滾動結束,需要重繪,主畫布會被清空,然後從快取畫布中根據行為上下文進行畫布偏移,將偏移後的圖層直接繪製在主畫布上,隨後在主畫布上繪製偏移後的剩餘部分,最後更新快取。

使用快取畫布和油畫分層機制,大大提升了繪製效能,使整個滾動過程更加流暢、順滑。

覺得不錯給點個贊吧~後續還會為大家帶來更多技術揭祕和有趣內容。

轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

相關文章