content-visibility#
改善初始加载时间,通过跳过屏幕外内容的渲染。
通常,許多網站在其頁面上有很複雜的圖形介面,並且一些內容可能會超出用戶瀏覽器視野範圍之外。在這種情況下,可以使用 CSS 中的 content-visibility
屬性來跳過螢幕外的內容渲染,以此來減少頁面的渲染時間。這個功能是最新版本的 CSS 新增的一個特性,並且對於提高渲染性能影響很大。 content-visibility
屬性可以有三個值,包括 visible
、auto
和 hidden
。但是,我們通常可以通過將 content-visibility
設置為 auto
來直接提高頁面的渲染性能,尤其是當頁面中存在大量的離屏內容時。本質上,這個屬性 改變了一個元素的可見性,並管理其渲染狀態。
content-visibility
的主要功能是允許我們推遲渲染 HTML 元素。瀏覽器默認情況下會渲染所有可見的元素,包括視窗可見區域之外不可見的 HTML 元素。這樣做可以讓瀏覽器正確計算頁面尺寸,並保持整個頁面佈局和滾動條的一致性。如果不渲染所有元素,滾動將變得混亂,因為瀏覽器無法計算頁面高度。
然而,content-visibility
會將分配給它的元素的高度視為 0,在渲染之前將其高度設置為 0。這可能導致頁面高度和滾動條混亂。但是,如果已經為元素或其子元素顯式設置了高度,則不會出現此問題。在沒有顯式設置高度的情況下,可以使用 contain-intrinsic-size
來確保元素正確渲染並保留延遲渲染的好處。
.card {
content-visibility: auto;
contain-intrinsic-size: 200px;
}
使用 contain-intrinsic-size
,可以確保沒有設置尺寸的 div(例如 .card)仍然佔據空間,並像具有固有尺寸的單個子元素一樣佈局。這個屬性作為一個佔位符,來替代渲染內容。
雖然使用 content-visibility: auto
可以減少頁面渲染時間,但如果有很多元素都設置了這個屬性,仍然可能會出現滾動條問題。
除了 content-visibility: auto
之外,content-visibility
還提供另外兩個值:visible
和 hidden
。這使我們能夠實現類似於 display: none
和非 none 值之間的切換,從而實現元素的顯式和隱藏。
在這種情況下,content-visibility
可以提高頻繁顯示或隱藏的元素的渲染性能,例如模態框的顯示和隱藏。content-visibility
可以提供這種性能提升,這要歸功於其隱藏值(hidden)的功能與其他值的不同:
-
display: none
:隱藏元素並破壞其渲染狀態。這意味著取消隱藏元素與渲染具有相同內容的新元素一樣昂貴 -
visibility: hidden
:隱藏元素並保持其渲染狀態。這並不能真正從文檔中刪除該元素,因為它(及其子樹)仍佔據頁面上的幾何空間,並且仍然可以單擊。它也可以在需要時隨時更新渲染狀態,即使隱藏也是如此 -
content-visibility: hidden
:隱藏元素並保留其渲染狀態。這意味著該元素隱藏時行為和 display: none 一樣,但再次顯示它的成本要低得多
References:
https://web.dev/content-visibility/
will-change#
在渲染 CSS 樣式之前,CSS 渲染器需要進行準備工作,因為某些 CSS 屬性需要進行大量準備才能實現渲染。這可能會導致頁面出現卡頓,給用戶帶來不良體驗。
比如,網頁上的動畫通常需要定期渲染,包括動態元素和其他元素。傳統的做法是使用 CSS 的 3D 變換(transform 中的 translate3d () 或 translateZ ())來開啟 GPU 加速,從而使動畫更流暢。但這種方法成本較高,可能會導致動畫延遲數百毫秒。
現在,可以使用 CSS 的 will-change
屬性來直接開啟 GPU 加速,而無需使用 transform
等 Hack 技巧。該屬性指示瀏覽器將修改特定屬性,以便進行必要的優化。這意味著 will-change
是一種提示,它不會對使用它的元素產生任何樣式上的影響。但需要注意的是,如果創建了新的層疊上下文,則可能會產生外觀效果。
瀏覽器渲染帶有 will-change
的元素時,瀏覽器將為該元素創建一個單獨的層。之後,它將該元素的渲染與其他優化一起委託給 GPU,即,瀏覽器會識別 will-change
屬性,並優化未來與不透明相關的變化。這將使動畫變得更加流暢,因為 GPU 加速接管了動畫的渲染
will-change
的使用並不複雜,它能接受的值有:
-
auto
:默認值,瀏覽器會根據具體情況,自行進行優化 -
scroll-position
:表示開發者將要改變元素的滾動位置,比如瀏覽器通常僅渲染可滾動元素 “滾動窗口” 中的內容。而某些內容超過該窗口(不在瀏覽器的可視區域內)。如果 will-change 顯式設置了該值,將擴展渲染 “滾動窗口” 周圍的內容,從而順利地進行更長,更快的滾動(讓元素的滾動更流暢) -
contents
:表示開發者將要改變元素的內容,比如瀏覽器常將大部分不經常改變的元素緩存下來。但如果一個元素的內容不斷發生改變,那麼產生和維護這個緩存就是在浪費時間。如果will-change
顯式設置了該值,可以減少瀏覽器對元素的緩存,或者完全避免緩存。變為從始至終都重新渲染元素。使用該值時需要盡量在文檔樹最末尾上使用,因為該值會被應用到它所聲明元素的子節點,要是在文檔樹較高的節點上使用的話,可能會對頁面性能造成較大的影響 -
<custom-ident>
:表示開發者將要改變的元素屬性。如果給定的值是縮寫,則默認被擴展全,比如,will-change
設置的值是padding
,那麼會補全所有padding
的屬性,如will-change: padding-top, padding-right, padding-bottom, padding-left;
使用 will-change
表示該元素在未來會發生變化#
因此,如果你試圖將 will-change
和動畫同時使用,它將不會給你帶來優化。因此,建議在父元素上使用 will-change
,在子元素上使用動畫。
.animate-element-parent {
will-change: opacity;
}
.animate-element {
transition: opacity .2s linear
}
不要使用非動畫元素#
當在一個元素上使用 will-change
時,瀏覽器會嘗試將該元素移動到一個新圖層,並通過 GPU 進行轉換來優化它。但如果沒有任何要轉換的內容,則會導致資源浪費。
此外,使用 will-change
也需要謹慎處理,MDN 網站提供了相關描述:
-
不要將
will-change
應用於太多元素:瀏覽器已經盡力優化一切可優化的東西了。一些更強大的優化措施可能與will-change
結合使用,這可能會消耗大量機器資源。如果過度使用,可能會導致頁面響應緩慢或佔用大量資源。例如,* { will-change: transform, opacity; }
-
適度使用:通常情況下,當元素恢復到其初始狀態時,瀏覽器會放棄之前的優化工作。但是,如果在樣式表中顯式聲明 will-change 屬性,則表示目標元素可能經常發生變化,因此瀏覽器將保留優化工作的時間比以前更長。最佳做法是在元素變化之前和之後通過腳本來切換 will-change 的值。
-
不要過早應用
will-change
優化:如果頁面在性能方面表現良好,則不應添加will-change
屬性來追求微小的速度提升。will-change
的設計初衷是為了解決現有性能問題而不是預防性能問題。過度使用will-change
會導致大量的內存佔用,並使渲染過程更加複雜,因為瀏覽器試圖準備可能存在的變化過程。這會導致更嚴重的性能問題。 -
給它足夠的工作時間:該屬性用於告知瀏覽器哪些屬性可能會發生變化。然後瀏覽器可以在變化發生前嘗試做出一些優化工作。因此,給瀏覽器足夠的時間去真正地做這些優化工作非常重要。使用時需要找到一些方法提前獲知元素可能發生的變化,並為其添加
will-change
屬性。
最後需要注意的是,建議在完成所有動畫後,將元素的 will-change
刪除。下面這個示例展示如何使用腳本正確地應用 will-change
屬性的示例,在大部分的場景中,你都應該這樣做。
var el = document.getElementById('element');
// 當鼠標移動到該元素上時給該元素設置 will-change 屬性
el.addEventListener('mouseenter', hintBrowser);
// 當 CSS 動畫結束後清除 will-change 屬性
el.addEventListener('animationEnd', removeHint);
function hintBrowser() {
// 填寫上那些你知道的,會在 CSS 動畫中發生改變的 CSS 屬性名們
this.style.willChange = 'transform, opacity';
}
function removeHint() {
this.style.willChange = 'auto';
}
讓元素及其內容盡可能獨立於文檔樹的其餘部分 (contain)#
CSS
contain
屬性讓你能夠向瀏覽器解釋你的佈局,以便進行性能優化。然而,它確實會對你的佈局產生一些副作用。
W3C 的 CSS Containment Module Level 2 除了提供前面介紹的 content-visibility
屬性之外,還有另一個屬性 contain
。該屬性允許我們指定特定的 DOM 元素及其子元素,使它們能夠獨立於整個 DOM 樹結構之外。目的是讓瀏覽器能夠只對部分元素進行重繪、重排,而不必每次針對整個頁面操作。也就是說,contain
允許瀏覽器只重新計算佈局、樣式、繪畫、大小或它們的任意組合,而針對 DOM 的有限區域,而不是整個頁面。
在實際使用中,我們可以通過 contain
屬性設置下面五個值中的某一個來規定元素以何種方式獨立於文檔樹:
layout
:該值表示元素的內部佈局不受外部的任何影響,同時該元素以及其內容也不會影響以上級別。paint
:該值表示元素的子級不能在該元素的範圍外顯示,該元素不會有任何內容溢出(或者即使溢出了,也不會被顯示)。size
:該值表示元素盒子的大小是獨立於其內容,也就是說,在計算該元素盒子大小時會忽略其子元素。content
:該值是contain: layout paint
的簡寫。strict
:該值是contain: layout paint size
的簡寫。
contain
的 size、layout 和 paint 提供了不同的方式來影響瀏覽器的渲染計算:
size
:告訴瀏覽器,當其內容發生變化時,該容器不應導致頁面上的位置移動。layout
:告訴瀏覽器,容器的後代不應該導致其容器外元素的佈局改變,反之亦然。paint
:告訴瀏覽器,容器的內容將永遠不會繪製超出容器的尺寸。如果容器是模糊的,則根本不會繪製內容。
References:
- https://css-tricks.com/lets-take-a-deep-dive-into-the-css-contain-property/
- https://www.smashingmagazine.com/2019/12/browsers-containment-css-contain-property/
使用 font-display
解決由於字體造成的佈局偏移(FOUT)#
在 Web 中,當使用非系統字體(通過 @font-face
規則引入的字體)時,瀏覽器可能無法及時獲取到 Web 字體,從而使用後備系統字體來渲染文本,這可能會導致未編排(Unstyled)的文本出現閃爍,並使整個頁面佈局偏移一下(FOUT)。
幸運的是,CSS 的 font-display
屬性定義了瀏覽器如何加載和顯示字體文件,允許文本在字體加載或加載失敗時顯示回退字體。這可以提高性能,通過折中無樣式文本閃爍使文本可見,而不是白屏等待。
CSS 的 font-display
屬性有五個值:
auto
:默認值。使用自定義字體的文本將被隱藏,直到字體加載完成才會顯示,與大多數瀏覽器的默認策略相似。block
:給字體一個較短的阻塞時間和無限大的交換時間,在字體加載完成前繪製 “隱形” 文本;一旦字體加載完成,立即切換字體。只有當使用特定字體渲染文本對頁面很重要時,才應該使用 block。swap
:阻塞時間為 0,交換時間無限大,在字體加載完成前立即繪製文字;一旦字體加載成功,立即切換字體。只有當使用特定字體渲染文本對頁面很重要,且使用其他字體渲染仍將顯示正確信息時,才應使用 swap。fallback
:在較短的時間內,需要使用自定義字體渲染的文本不可見;如果字體還未加載完成,則先加載無樣式的文本。一旦字體加載成功,文本將被正確賦予樣式。當等待時間過久時,頁面將一直使用後備字體。如果希望用戶儘快開始閱讀,而且不因新字體的載入導致文本樣式發生變動而干擾用戶體驗,fallback 是一個很好的選擇。optional
:與 fallback 類似,都是在極短的時間內文本不可見,然後再加載無樣式的文本。但是,optional 選項可以讓瀏覽器自由決定是否使用自定義字體,這取決於瀏覽器的連接速度。如果速度很慢,那麼自定義字體可能不會被使用。使用 optional 時,阻塞時間應該非常小,交換時間為 0。
@font-face {
font-family: "Open Sans Regular";
font-weight: 400;
font-style: normal;
src: url("fonts/OpenSans-Regular-BasicLatin.woff2") format("woff2");
font-display: swap;
}
References:
- https://iamschulz.com/a-deep-dive-into-webfonts/
- https://simonhearne.com/2021/layout-shifts-webfonts/
- https://css-tricks.com/the-best-font-loading-strategies-and-how-to-execute-them/
- https://calendar.perfplanet.com/2020/a-font-display-setting-for-slow-connections/
- https://css-tricks.com/how-to-load-fonts-in-a-way-that-fights-fout-and-makes-lighthouse-happy/
- https://nooshu.github.io/blog/2021/01/23/the-importance-of-font-face-source-order-when-used-with-preload/
- https://csswizardry.com/2020/05/the-fastest-google-fonts/
- https://www.zachleat.com/web/comprehensive-webfonts/
scroll-behavior#
scroll-behavior 是 CSSOM View Module 提供的一個新特性,可以幫助我們實現流暢的滾動效果。該屬性可以為一個滾動框指定滾動行為,不會影響由用戶操作產生的其他任何滾動。
scroll-behavior 有兩個值:
auto
:表示滾動框立即滾動。smooth
:表示滾動框使用定義的時間函數,在一段用戶代理定義的時間段內平滑地滾動。請注意,如果存在,則用戶代理平台應遵循約定。
開啟 GPU 渲染動畫#
瀏覽器針對處理 CSS 動畫和不會很好地觸發重排(因此也導致繪)的動畫屬性進行了優化。為了提高性能,可以將被動畫化的節點從主線程移到 GPU 上。將導致合成的屬性包括 3D transforms (transform: translateZ()
, rotate3d()
,等),animating, transform 和 opacity, position: fixed,will-change,和 filter。一些元素,例如 <video>
, <canvas>
和 <iframe>
,也位於各自的圖層上。將元素提升為圖層(也稱為合成)時,動畫轉換屬性將在 GPU 中完成,從而改善性能,尤其是在移動設備上。
減少渲染阻止時間#
大樣式表分解成多個樣式表,只讓主 CSS 文件阻塞關鍵路徑,並以高優先級下載它,而讓其他樣式表以低優先級方式下載。
<!-- style.css contains only the minimal styles needed for the page rendering -->
<link rel="stylesheet" href="styles.css" media="all" />
<!-- Following stylesheets have only the styles necessary for the form factor -->
<link rel="stylesheet" href="sm.css" media="(min-width: 20em)" />
<link rel="stylesheet" href="md.css" media="(min-width: 64em)" />
<link rel="stylesheet" href="lg.css" media="(min-width: 90em)" />
<link rel="stylesheet" href="ex.css" media="(min-width: 120em)" />
<link rel="stylesheet" href="print.css" media="print" />
默認情況下,瀏覽器假設每個指定的樣式表都是阻塞渲染的。通過添加 media 屬性附加媒體查詢,告訴瀏覽器何時應用樣式表。當瀏覽器看到一個它知道只會用於特定場景的樣式表時,它仍會下載樣式,但不會阻塞渲染。通過將 CSS 分成多個文件,主要的 阻塞渲染 文件(本例中為 styles.css)的大小變得更小,從而減少了渲染被阻塞的時間。