雖然我們時常聽到盡量不要操作 DOM,但在 UI 上要完全不操作 DOM ,除非你將應用全部用 canvas 改寫,不然根本是不可能的事。
今天就來淺談前端中的效能問題。
圖片源自 Google Developer
這張圖相信比較有經驗的前端工程師們都看過或是類似的圖。在瀏覽器的渲染過程中主要會有這幾個階段。
其中,影響渲染效能最多的就是 layout 這個階段了。為什麼呢?假設我們今天在一個 div
上將 height: 300px
改為 height: 400px
,會發生什麼事呢?因為他的高度改變了,父元素的高也可能改變、父元素的父元素的高也可能改變,追溯到最後會發現 layout 涉及的範圍會是整個 document,一旦 layout 的次數太頻繁,再加上元素內的結構複雜,就很可能有卡頓的情況發生。
既然 layout 無法避免,我們可以試著讓 layout 的次數減少一些,要讓 layout 的次數減少,我們首先要知道什麼情況下 layout 會發生:
如果使用 JavaScript 來存取這些屬性時,為了保證拿到的 DOM 屬性值是最新的,有可能會強制瀏覽器再次更新 layout,這通常被叫做 forced synchronous layout,如果用 Developer tool 查看,會看見紅紅的三角形。
要減少 layout 的次數有幾個方法:
DocumentFragment
方便操作transform
代替像是 top, bottom 的操作,讓瀏覽器幫你額外建立新的圖層requestAnimationFrame
:這個 API 會幫你將函式 schedule 在每個 frame 之間,並且在下一次 paint 之前執行。一般的動畫當中,如果用像是 left, right 的話,會不斷觸發 layout,造成效能降低。
如果使用 GPU 加速,browser 會幫你建立一個 layer,並且 composite,並不會觸發 layout,所以不會影響到其他元素的佈局。
不過要注意的是,因為不會影響到其他元素的佈局,代表著如果你使用 transform
的方式來實作 dropdown 的話,有可能會得到下列的結果。
要建立額外圖層,通常有幾個條件:
backface-visibility: hidden
will-change: transform
transform
目前在無限滾動列表時,主要已經收斂成幾個方案來提升效能:
IntersectionObserver
來實作無限滾動,但使用者只能被動滑內容Google developer 上有一系列關於效能優化的文章值得參考:
雖然還是老話一句,過早優化是萬惡的根源,但像是在 for...loop 裡面不斷呼叫 getBoundingRect
或是用 top, left 做複雜的動畫等,這些聽起來就很可怕的操作可以在事先就預防。
另外也不需要過往矯正,只要聽到要 layout 就硬要用 transform 或其他方式改寫,其實瀏覽器裡頭也做了相當多的優化,只要不要亂寫以及掌握幾個原則,還是可以在兼顧效能的情況下做開發。另外像是 React, Vue 等等內部也為了效能做了許多優化,前端開發也算是越來越幸福了...吧。