# 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」(如下圖)...

- 因為瀏覽器得下載與解析 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 可以使用,例如[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 來完成

| | CPU| GPU |
| -------- | -------- | -------- |
| 全名 | Central Processing Unit 中央處理器 | Graphics Processing Unit 圖形處理器 |
| 組成元件 | 控制單元、算術邏輯單元(ALU)、快取及動態隨機存取記憶體(DRAM) | 同左,但設計較為簡易,且數量較多 |
| 運算模式 | 單控制流單資料流 | 單控制流多資料流 |
| 比喻 | 一個大學生 | 一群小學生 |
| 使用情景 | 邏輯複雜(通才型處理器) | 平行計算大批量的重複任務 |
### GPU 硬體加速的原理和分層有關
> 過程:render tree -> 渲染元素-> 圖層-> GPU 渲染-> 瀏覽器複合圖層-> 產生最終的螢幕影像。


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 - 設定 - 系統

- 網址列輸入 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 上進行處理,以提高性能。 -->