Try   HackMD

Performance Chapter 12-13

12. Writing High Performance CSS

  • 其實瀏覽器解析 CSS 的速度非常快
  • 不能因為 CSS不會對性能產生「巨大」的影響,就自顧自的亂寫一通

效能優化是一段追求好還要更好的過程。

CSS Selector

  • CSS Selector 的匹配原則是「從右向左進行的」
  • 這個最右側的選擇器又被稱作「關鍵選擇器」。
#ironman .article h2 { /* Some Style */ } /* 先找到 DOM 中所有的 h2 元素, * 再 filter 掉祖先元素不是 .article 的, * 最後再 filter 掉祖先元素不是 #ironman 的元素 * 直教使用#ironman-article-h2較佳 */

不同 CSS Selector 的性能

1. ID selector ID選擇器
2. Class Selector 類別選擇器
3. Element Selector 元素選擇器 - span
4. General Sibling Combinator 兄弟選擇器 - img + p
5. Child Combinator 子選擇器 - div > span
6. Descendant Combinator 後代選擇器 - div span
7. Attribute Selector 屬性選擇器 - a[title]
8. Pseudo Element/Class Selector 偽元素選擇器 - p::first-line
  • 效能排列從上到下效能由高到低 (ID Selector 與 Class Selector 效能其實差異不大)

CSS Selector 最佳化的 Tips

  • 千萬不要亂用萬用字元 * (想像成SQL的SELECT * FROM)
  • 避免在 ID 或是 Class Selector 前面限定元素類型
div#navbar {} p.ironman {}
  • Child Combinator 子選擇器 優先於 Descendant Combinator 後代選擇器
    • 目標:找包在 div 裡面的 p tag(一層的父子關係),div>p 的效能優於 div p
  • 善用 CSS 屬性繼承的特性
/* This is not good */ #ironman {} #ironman > .part1 { color: blue; } #ironman > .part2 { color: blue; } /* This is better */ #ironman { color: blue; }

非必要的情況,減少使用昂貴的 Attribute

  • 昂貴的屬性指的是一些需要瀏覽器進行操作或計算的屬性,它們需要耗費更多的瀏覽器性能。
:hover
:nth-child
filter
opacity
box-shadow 
border-radius
...
...
  • 這些都是很常見且很重要的屬性,不是禁用,而是思考是不是有其他替代方案。
  • 關於替代方案 (By ChatGPT)
    • :hover 替代方案:
      • 考慮使用click事件代替hover事件。
      • 使用 will-change 屬性。
    • :nth-child 屬性替代方案:
      • 使用 class選擇器。
    • filter 屬性替代方案:
      • 考慮使用其他技術,如 SVG 濾鏡來實現。
    • box-shadow 屬性替代方案:
      • 使用適量的陰影,或在不影響性能的情況下考慮使用其他技術,如 SVG 濾鏡。
    • border-radius 屬性替代方案:
      • 減少 border-radius 的半徑值,以降低性能開銷。
      • 使用圖像替代圓角。
    • opacity 屬性替代方案:
      • 考慮使用 RGBA 。

最佳化 Reflow & Repaint

減少 Reflow

  • Reflow 是指瀏覽器為了重新布局而重新計算元素的幾何屬性的過程。
    • 改變元素的 margin 或 padding
    • 改變元素的 width, height, position 的 left 或 top
    • 改變 font-size 或 font-family
    • 改變視窗大小

減少 Repaint

  • Repaint 是指瀏覽器根據元素的樣式屬性重新繪製元素的過程,而不影響其布局。
    • 修改元素的背景色、文字顏色等外觀相關的樣式。
    • 使用擴展屬性,例如box-shadow或outline。
    • 對元素應用透明度。

  • 不是說禁用這些屬性,而是使用前思考是否有別的方式可以完成需求
  • 關於替代方案 (By ChatGPT)
      1. Minimize Reflow:
      • 案例:處理元素展開(或收縮)的動畫
      ​​​​​​​修正前: ​​​​​​.collapsible { ​​​​​​​​ transition: height 0.3s ease; ​​​​​​​​} ​​​​​​​​.expanded { ​​​​​​​​ height: 200px; ​​​​​​​​} ​​​​​​​​修正後: ​​​​​​​​.collapsible { ​​​​​​​​ transition: transform 0.3s ease; /* 使用 transform 替代 height */ ​​​​​​​​} ​​​​​​​​.expanded { ​​​​​​​​ transform: scaleY(2); /* 這樣只需要操作 transform,不會觸發 reflow */ ​​​​​​​​} ​​​​​​​​
      1. Batch Style Changes for Repaint:
      • 將需要改變的樣式集中在一個操作中,以最小化Repaint的發生次數
      • 案例:切換主題模式
      ​​​​​​​​// 不佳的方式,可能觸發多次 Repaint ​​​​​​​​element.style.color = "red"; ​​​​​​​​element.style.backgroundColor = "blue"; ​​​​​​​​element.style.fontSize = "16px"; ​​​​​​​​element.style.cssText = "color: red; background-color: blue; font-size: 16px;"; ​​​​​​​​element.classList.add("batch-modifications");
      1. Avoid Forced Synchronous Layout:
      • 改用非強制同步的方式獲取元素的位置信息。
      • 案例:獲取元素的位置信息以進行動畫效果。
      ​​​​​​​​const leftPosition = element.offsetLeft; // 強制同步佈局 ​​​​​​​​const leftPosition = element.getBoundingClientRect().left; // 使用非強制同步的方法
      • getBoundingClientRect() 是用於獲取元素在視窗中位置和尺寸的方法。通常情況下,它是異步執行的,等待瀏覽器完成布局和繪製後才返回。
      1. Defer Changes:
      • 將可能觸發Reflow和Repaint的操作延遲到下一個動畫幀。
      • 案例:在滾動時實時更新元素的樣式。
      ​​​​​​​​window.addEventListener('scroll', () => { ​​​​​​​​ requestAnimationFrame(() => { ​​​​​​​​ // 在動畫幀中執行操作,最大程度上減少重排和重繪的影響 ​​​​​​​​ }); ​​​​​​​​});

Extract Critical CSS

如果你的網頁有很糟的 First Contentful Paint (FCP),並且在Lighthouse看到「Eliminate render-blocking resource」(如下圖)

  • 因為瀏覽器得下載與解析 CSS 後才能呈現頁面,所以 CSS 是一個「Render-Blocking-Resource」。

Critical CSS 是一種最佳化網頁性能的技術,主要目標是加速首次載入頁面的速度,特別是針對頁面的上方(above-the-fold)內容,這些內容是用戶首次看到並在滾動之前可見的部分。

above-the-fold content 的範圍並沒有一個明確的界定值,因為每個裝置的大小與螢幕尺寸都不會一樣。

Critical CSS就是渲染首屏的最小CSS集合。

  • 找出關鍵 CSS,嵌入到HTML 文檔的<head>中。

  • 剩餘非關鍵 CSS(即不在首屏可見區域的 CSS),可以留在外部 CSS 文件中,但可進行最佳化,例如壓縮和延遲載入。

  • 優缺點

    • 優點
      • 減少 FCP 的時間
      • 不必透過額外網路請求去抓取
    • 缺點
      • 代碼維護不易
      • 不容易緩存:inlined CSS 沒有辦法像外部 CSS 檔案一樣被瀏覽器快取。
      • 增加 HTML 文件的大小:這部分跟 TCP 的機制有關

        New TCP connections cannot immediately use the full available bandwidth between the client and the server, they all go through slow-start to avoid overloading the connection with more data than it can carry. In this process, the server starts the transfer with a small amount of data and if it reaches the client in perfect condition, doubles the amount in the next roundtrip. For most servers, 10 packets or approximately 14 KB is the maximum that can be transferred in the first roundtrip.
        (From web.dev 文件)

        • 為了最大限度地減少首次渲染的次數,應將首屏內容(不需捲動位置的內容)保持在 14 KB 以下。
  • 實作:有一些 npm module 可以使用,例如criticalcriticalCSSpentHouse

Preload

​​​​<link rel="preload" />
  • Preloading resources defined in CSS

    • backgroumd-image, @font-face, media queries, 偽類等等
  • Preloading CSS files

    • Non-Critical CSS 就可以根據自己的需求適時使用 preload 來提早開始下載的時機

小結

  • 學會撰寫 high performance CSS:有些技術要不要使用得依照實際狀況謹慎考慮,畢竟大多數提升性能的背後都有它們的 trade off 在。
  • 建立思維:寫 CSS 時去想一下 reflow repaint 的流程,下手前先思考一下有沒有更好的解法。

13. CSS GPU Acceleration

主軸:如何讓網站達到更順暢的動畫體驗與效能

  • Hardware Acceleration 硬體加速 : 瀏覽器會把一些比較複雜的頁面渲染相關任務交給 GPU 處理,而不是全部都靠 CPU 來完成

CPU GPU
全名 Central Processing Unit 中央處理器 Graphics Processing Unit 圖形處理器
組成元件 控制單元、算術邏輯單元(ALU)、快取及動態隨機存取記憶體(DRAM) 同左,但設計較為簡易,且數量較多
運算模式 單控制流單資料流 單控制流多資料流
比喻 一個大學生 一群小學生
使用情景 邏輯複雜(通才型處理器) 平行計算大批量的重複任務

GPU 硬體加速的原理和分層有關

過程:render tree -> 渲染元素-> 圖層-> GPU 渲染-> 瀏覽器複合圖層-> 產生最終的螢幕影像。

image
image

  1. 主執行緒(Main Thread):
  • Render Tree 的構建
  • 渲染元素:將Render Tree每個節點映射到相應的渲染器。
  • 圖層的創建: 根據一些規則,主執行緒可能會將某些渲染器分配到單獨的圖層中。
  • 佈局計算(Layout):確定每個渲染器的確切位置和大小。
  • 繪製(Paint):繪製每個元素的內容,包括文字、顏色和背景等。
  1. 合成執行緒(Compositor Thread):
  • 圖層的合成:接收主執行緒提供的圖層訊息合成最終的網頁視圖,並利用 GPU 資源進行硬體加速的渲染。
  • 動畫處理: 處理滾動、CSS 動畫等動畫效果,確保視覺呈現的平順性。
  • 獨立圖層樹的維護
  1. 瀏覽器複合圖層(Browser Composite Layer):
  • 合成請求的生成: 如果有需要,主執行緒會生成合成請求,標記哪些部分的畫面需要重新合成。
  • 瀏覽器複合圖層的生成:由主執行緒和合成執行緒之間的協同操作,將合成執行緒生成的圖層和主執行緒生成的其他圖層整合在一起。
  1. 產生最終的螢幕影像

  • 在網頁渲染的過程中,有些元素會因為符合了某些規則,而被提升為獨立的圖層。
  • GPU 會運行在獨立的 Process 裡,不會 block 住 Render Engine 的其他工作。

範例

TIPS:
chrome devtools 開啟Rendering 中的Layer borders 查看圖層紋理。
黃色邊框表示該元素有3d 變換,表示放到新的複合層(composited layer)中渲染。

建立獨立圖層

哪些規則能讓瀏覽器主動幫我們建立獨立的圖層呢?(By ChatGPT)

  1. 3D 變換和透視(3D Transforms and Perspective):
    • 例如 : transform: translate3d 或 transform: perspective 屬性。
  2. 加速屬性(Accelerated Properties):
    • 例如 : will-change 屬性。
  3. 視口外的元素(Offscreen Elements)
  4. 具有硬體加速屬性的動畫元素(Animating Elements with Hardware Acceleration)
  5. 遮罩和裁剪(Masking and Clipping)
  6. 視口內的可滾動元素(Scrollable Elements within the Viewport):
    • 例如 : verflow: auto 或 overflow: scroll 屬性的元素。
  7. Canvas 元素(Canvas Elements)

如何確認瀏覽器有沒有支援硬體加速

  • Chrome - 右上角三個點的 icon - 設定 - 系統

  • 網址列輸入 chrome://gpu 檢視目前你的 Chrome 瀏覽器中有哪些功能是開啟 GPU 加速的

如何啟用硬體加速?

有一些 CSS 屬性被歸類於 GPU accelerated properties,也就是被瀏覽器認定為「有機會」被 GPU 加速的屬性:

  1. transform
  2. opacity
  3. filter

transform hack

如果有些元素不需要用到上述屬性,但是需要觸發硬體加速效果,可以使用一些小技巧來誘導瀏覽器開啟硬體加速。

transform: translateZ(0); transform: rotateZ(360deg); transform: translate3d(0, 0, 0); example: transform: translate(0, 20px); 讓元素向下移動 20px,你可以使用以下的方法來開啟硬體加速 transform: translate3D(0, 20px, 0);
  • 缺點:
    • memory 的使用量急遽提升 => 手機的瀏覽器直接 crash
    • GPU 渲染會影響字體的抗鋸齒效果 => 文字模糊
  • 解決方式:測試結果說話(ex: Chrome DevTools的Performance面板,記錄每個用例的性能指標,如渲染時間、幀率、記憶體使用量等。)

will-change property

  • 新的 CSS 屬性:不必依賴 transform hack來開啟硬體加速。
  • 讓瀏覽器可以先行準備並「讓瀏覽器選擇」最好的方式來處理這個變動。
  • 例如:CSS transform => 螢幕閃爍
will-change: transform; /* 也可以一次給多個 */ will-change: transform, opacity;

使用 will-change 的 Tips

  • 不要 will-change 套用在根本不會需有用到的元素
* {
    will-change: transform;
}

小結

  • 分層是 GPU 硬體加速的關鍵,允許瀏覽器僅重繪和重排變化的部分,而不是整個網頁,這對於提高性能和渲染速度至關重要。
  • 善用將經常變換的DOM主動提升到獨立的層的規則,那麼在瀏覽器的一幀運行中,就可以減少Layout 和Paint 的時間了。

參考資料

https://dev.to/arikaturika/how-web-browsers-work-the-render-tree-part-7-with-illustrations-24h3
https://ithelp.ithome.com.tw/articles/10273656
https://cloud.tencent.com/developer/article/1906655