# Performance Chapter 12-13 ## 12. Writing High Performance CSS - 其實瀏覽器解析 CSS 的速度非常快 - 不能因為 CSS不會對性能產生「巨大」的影響,就自顧自的亂寫一通 > 效能優化是一段追求好還要更好的過程。 ### CSS Selector - CSS Selector 的匹配原則是「從右向左進行的」 - 這個最右側的選擇器又被稱作「關鍵選擇器」。 ```css= #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 前面限定元素類型 ```css= div#navbar {} p.ironman {} ``` - Child Combinator 子選擇器 優先於 Descendant Combinator 後代選擇器 - 目標:找包在 div 裡面的 p tag(一層的父子關係),div>p 的效能優於 div p - 善用 CSS 屬性繼承的特性 ```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: - 案例:處理元素展開(或收縮)的動畫 ```js= 修正前: .collapsible { transition: height 0.3s ease; } .expanded { height: 200px; } 修正後: .collapsible { transition: transform 0.3s ease; /* 使用 transform 替代 height */ } .expanded { transform: scaleY(2); /* 這樣只需要操作 transform,不會觸發 reflow */ } ``` - 2. Batch Style Changes for Repaint: - 將需要改變的樣式集中在一個操作中,以最小化Repaint的發生次數 - 案例:切換主題模式 ```js= // 不佳的方式,可能觸發多次 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"); ``` - 3. Avoid Forced Synchronous Layout: - 改用非強制同步的方式獲取元素的位置信息。 - 案例:獲取元素的位置信息以進行動畫效果。 ```js= const leftPosition = element.offsetLeft; // 強制同步佈局 const leftPosition = element.getBoundingClientRect().left; // 使用非強制同步的方法 ``` - getBoundingClientRect() 是用於獲取元素在視窗中位置和尺寸的方法。通常情況下,它是異步執行的,等待瀏覽器完成布局和繪製後才返回。 - 4. Defer Changes: - 將可能觸發Reflow和Repaint的操作延遲到下一個動畫幀。 - 案例:在滾動時實時更新元素的樣式。 ```js= window.addEventListener('scroll', () => { requestAnimationFrame(() => { // 在動畫幀中執行操作,最大程度上減少重排和重繪的影響 }); }); ``` ### Extract Critical CSS > 如果你的網頁有很糟的 First Contentful Paint (FCP),並且在Lighthouse看到「Eliminate render-blocking resource」(如下圖)... ![](https://hackmd.io/_uploads/BJxdXnoxT.png) - 因為瀏覽器得下載與解析 CSS 後才能呈現頁面,所以 CSS 是一個「Render-Blocking-Resource」。 > Critical CSS 是一種最佳化網頁性能的技術,主要目標是加速首次載入頁面的速度,特別是針對頁面的上方(above-the-fold)內容,這些內容是用戶首次看到並在滾動之前可見的部分。 ![](https://hackmd.io/_uploads/rJpxJnoe6.png) above-the-fold content 的範圍並沒有一個明確的界定值,因為每個裝置的大小與螢幕尺寸都不會一樣。 > Critical CSS就是渲染首屏的最小CSS集合。 - 找出關鍵 CSS,嵌入到HTML 文檔的<head>中。 ![](https://hackmd.io/_uploads/SkZyJnjlT.png) - 剩餘非關鍵 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 可以使用,例如[critical](https://github.com/addyosmani/critical)、[criticalCSS](https://github.com/filamentgroup/criticalCSS)、[pentHouse](https://github.com/pocketjoso/penthouse) ### 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 來完成 ![](https://hackmd.io/_uploads/HJlaof9-a.png) | | CPU| GPU | | -------- | -------- | -------- | | 全名 | Central Processing Unit 中央處理器 | Graphics Processing Unit 圖形處理器 | | 組成元件 | 控制單元、算術邏輯單元(ALU)、快取及動態隨機存取記憶體(DRAM) | 同左,但設計較為簡易,且數量較多 | | 運算模式 | 單控制流單資料流 | 單控制流多資料流 | | 比喻 | 一個大學生 | 一群小學生 | | 使用情景 | 邏輯複雜(通才型處理器) | 平行計算大批量的重複任務 | ### GPU 硬體加速的原理和分層有關 > 過程:render tree -> 渲染元素-> 圖層-> GPU 渲染-> 瀏覽器複合圖層-> 產生最終的螢幕影像。 ![image](https://hackmd.io/_uploads/Sk1sbW0X6.png) ![image](https://hackmd.io/_uploads/Bk4n--RXa.png) 1. 主執行緒(Main Thread): - Render Tree 的構建 - 渲染元素:將Render Tree每個節點映射到相應的渲染器。 - 圖層的創建: 根據一些規則,主執行緒可能會將某些渲染器分配到單獨的圖層中。 - 佈局計算(Layout):確定每個渲染器的確切位置和大小。 - 繪製(Paint):繪製每個元素的內容,包括文字、顏色和背景等。 2. 合成執行緒(Compositor Thread): - 圖層的合成:接收主執行緒提供的圖層訊息合成最終的網頁視圖,並利用 GPU 資源進行硬體加速的渲染。 - 動畫處理: 處理滾動、CSS 動畫等動畫效果,確保視覺呈現的平順性。 - 獨立圖層樹的維護 3. 瀏覽器複合圖層(Browser Composite Layer): - 合成請求的生成: 如果有需要,主執行緒會生成合成請求,標記哪些部分的畫面需要重新合成。 - 瀏覽器複合圖層的生成:由主執行緒和合成執行緒之間的協同操作,將合成執行緒生成的圖層和主執行緒生成的其他圖層整合在一起。 4. 產生最終的螢幕影像 --- - 在網頁渲染的過程中,有些元素會因為符合了某些規則,而被提升為獨立的圖層。 - GPU 會運行在獨立的 Process 裡,不會 block 住 Render Engine 的其他工作。 [範例](https://lz5z.com/css3_hardware_speedup/) > 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 - 設定 - 系統 ![](https://hackmd.io/_uploads/B10QV75Wp.png) - 網址列輸入 chrome://gpu 檢視目前你的 Chrome 瀏覽器中有哪些功能是開啟 GPU 加速的 ### 如何啟用硬體加速? 有一些 CSS 屬性被歸類於 **GPU accelerated properties**,也就是被瀏覽器認定為「有機會」被 GPU 加速的屬性: 1. transform 2. opacity 3. filter ### transform hack 如果有些元素不需要用到上述屬性,但是需要觸發硬體加速效果,可以使用一些小技巧來誘導瀏覽器開啟硬體加速。 ```css= 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 => 螢幕閃爍 ```css= 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 <!-- 昂貴屬性: opacity 屬性通常被認為是昂貴的屬性,因為當元素的透明度發生變化時,可能會導致瀏覽器進行重繪(Repaint)操作。 硬體加速屬性: 在某些情況下,opacity 變化也可以觸發硬體加速。在一些現代瀏覽器中,當對一個元素應用硬體加速變換(例如 transform: translateZ(0))時,opacity 的變化可能會在 GPU 上進行處理,以提高性能。 -->