使用 Content-Visibility 優(yōu)化渲染性能
最近在業(yè)務(wù)中實(shí)際使用 content-visibility 進(jìn)了一些渲染性能的優(yōu)化。
這是一個(gè)比較新且有強(qiáng)大功能的屬性。本文將帶領(lǐng)大家深入理解一番。
何為content-visibility?
content-visibility:屬性控制一個(gè)元素是否渲染其內(nèi)容,它允許用戶代理(瀏覽器)潛在地省略大量布局和渲染工作,直到需要它為止。
MDN 原文:The content-visibility CSS property controls whether or not an element renders its contents at all, along with forcing a strong set of containments, allowing user agents to potentially omit large swathes of layout and rendering work until it becomes needed. Basically it enables the user agent to skip an element's rendering work (including layout and painting) until it is needed — which makes the initial page load much faster.
它有幾個(gè)常見(jiàn)的取值。
/* Keyword values */
content-visibility: visible;
content-visibility: hidden;
content-visibility: auto;
分別解釋一下:
- content-visibility: visible?:默認(rèn)值,沒(méi)有任何效果,相當(dāng)于沒(méi)有添加content-visibility,元素的渲染與往常一致。
- content-visibility: hidden?:與display: none 類似,用戶代理將跳過(guò)其內(nèi)容的渲染。(這里需要注意的是,跳過(guò)的是內(nèi)容的渲染)
- content-visibility: auto:如果該元素不在屏幕上,并且與用戶無(wú)關(guān),則不會(huì)渲染其后代元素。
contain-intrinsic-size
當(dāng)然,除 content-visibility? 之外,還有一個(gè)與之配套的屬性 -- contain-intrinsic-size。
contain-intrinsic-size?:控制由 content-visibility 指定的元素的自然大小。
上面兩個(gè)屬性光看定義和介紹會(huì)有點(diǎn)繞。
我們首先來(lái)看看 content-visibility 如何具體使用。
content-visibility: visible 是默認(rèn)值,添加后沒(méi)有任何效果,我們就直接跳過(guò)。
利用 ?content-visibility: hidden 優(yōu)化展示切換性
首先來(lái)看看 content-visibility: hidden?,它通常會(huì)拿來(lái)和 display: none 做比較,但是其實(shí)它們之間還是有很大的不同的。
首先,假設(shè)我們有兩個(gè) DIV 包裹框:
<div class="g-wrap">
<div>1111</div>
<div class="hidden">2222</div>
</div>
設(shè)置兩個(gè) div 為 200x200 的黑色塊:
.g-wrap > div {
width: 200px;
height: 200px;
background: #000;
}
效果如下:
OK,沒(méi)有問(wèn)題,接下來(lái),我們給其中的 .hidden? 設(shè)置 content-visibility: hidden,看看會(huì)發(fā)生什么:
.hidden {
content-visibility: hidden;
}
效果如下:
注意,仔細(xì)看效果,這里添加了 content-visibility: hidden 之后,消失的只是添加了該元素的 div 的子元素消失不見(jiàn),而父元素本身及其樣式,還是存在頁(yè)面上的。
如果我們?nèi)サ粼O(shè)置了 content-visibility: hidden? 的元素本身的 width、height、padding、margin 等屬性,則元素看上去就如同設(shè)置了 display: none 一般,在頁(yè)面上消失不見(jiàn)了。
那么,content-visibility: hidden 的作用是什么呢?
設(shè)置了 content-visibility: hidden? 的元素,其元素的子元素將被隱藏,但是,它的渲染狀態(tài)將會(huì)被緩存。所以,當(dāng) content-visibility: hidden 被移除時(shí),用戶代理無(wú)需重頭開(kāi)始渲染它和它的子元素。
因此,如果我們將這個(gè)屬性應(yīng)用在一些一開(kāi)始需要被隱藏,但是其后在頁(yè)面的某一時(shí)刻需要被渲染,或者是一些需要被頻繁切換顯示、隱藏狀態(tài)的元素上,其渲染效率將會(huì)有一個(gè)非常大的提升。
利用 content-visibility: auto 實(shí)現(xiàn)懶加載或虛擬列表
OK,接下來(lái)是 content-visibility? 的核心用法,利用 auto 屬性值。
content-visibility: auto 的作用是,如果該元素不在屏幕上,并且與用戶無(wú)關(guān),則不會(huì)渲染其后代元素。是不是與 LazyLoad 非常類似?
我們來(lái)看這樣一個(gè) DEMO ,了解其作用:
假設(shè),我們存在這樣一個(gè) HTML 結(jié)構(gòu),含有大量的文本內(nèi)容:
<div class="g-wrap">
<div class="paragraph">...</div>
// ... 包含了 N 個(gè) paragraph
<div class="paragraph">...</div>
</div>
每個(gè) .paragraph 的內(nèi)容如下:
因此,整個(gè)的頁(yè)面看起來(lái)就是這樣的:
由于,我們沒(méi)有對(duì)頁(yè)面內(nèi)容進(jìn)行任何處理,因此,所有的 .paragraph 在頁(yè)面刷新的一瞬間,都會(huì)進(jìn)行渲染,看到的效果就如上所示。
當(dāng)然,現(xiàn)代瀏覽器愈加趨于智能,基于這種場(chǎng)景,其實(shí)我們非常希望對(duì)于仍未看到,仍舊未滾動(dòng)到的區(qū)域,可以延遲加載,只有到我們需要展示、滾動(dòng)到該處時(shí),頁(yè)面內(nèi)容才進(jìn)行渲染。
基于這種場(chǎng)景,content-visibility: auto 就應(yīng)運(yùn)而生了,它允許瀏覽器對(duì)于設(shè)置了該屬性的元素進(jìn)行判斷,如果該元素當(dāng)前不處于視口內(nèi),則不渲染該元素。
我們基于上述的代碼,只需要最小化,添加這樣一段代碼:
.paragraph {
content-visibility: auto;
}
再看看效果,仔細(xì)觀察右側(cè)的滾動(dòng)條:
這里我使用了 ::-webkit-scrollbar 相關(guān)樣式,讓滾動(dòng)條更明顯。
可能你還沒(méi)意識(shí)到發(fā)生了什么,我們對(duì)比下添加了 content-visibility: auto? 和沒(méi)有添加 content-visibility: auto 的兩種效果下文本的整體高度:
有著非常明顯的差異,這是因?yàn)?,設(shè)置了 content-visibility: auto 的元素,在非可視區(qū)域內(nèi),目前并沒(méi)有被渲染,因此,右側(cè)內(nèi)容的高度其實(shí)是比正常狀態(tài)下少了一大截的。
好,我們實(shí)際開(kāi)始進(jìn)行滾動(dòng),看看會(huì)發(fā)生什么:
由于下方的元素在滾動(dòng)的過(guò)程中,出現(xiàn)在視口范圍內(nèi)才被渲染,因此,滾動(dòng)條出現(xiàn)了明顯的飄忽不定的抖動(dòng)現(xiàn)象。(當(dāng)然這也是使用了 content-visibility: auto 的一個(gè)小問(wèn)題之一),不過(guò)明顯可以看出,這與我們通常使用 JavaScript 實(shí)現(xiàn)的懶加載或者延遲加載非常類似。
當(dāng)然,與懶加載不同的是,在向下滾動(dòng)的過(guò)程中,上方消失的已經(jīng)被渲染過(guò)且消失在視口的元素,也會(huì)因?yàn)橄г谝暱谥校匦卤浑[藏。因此,即便頁(yè)面滾動(dòng)到最下方,整體的滾動(dòng)條高度還是沒(méi)有什么變化的。
content-visibility是否能夠優(yōu)化渲染性能?
那么,content-visibility 是否能夠優(yōu)化渲染性能呢?
在 Youtube -- Slashing layout cost with content-visibility[1] 中,給了一個(gè)非常好的例子。
這里我簡(jiǎn)單復(fù)現(xiàn)一下。
對(duì)于一個(gè)存在巨量 HTML 內(nèi)容的頁(yè)面,譬如類似于這個(gè)頁(yè)面 -- HTML - Living Standard[2]
可以感受到,往下翻,根本翻不到盡頭。(這里我在本地模擬了該頁(yè)面,復(fù)制了該頁(yè)面的所有 DOM,并非實(shí)際在該網(wǎng)站進(jìn)行測(cè)試)
如果不對(duì)這個(gè)頁(yè)面做任何處理,看看首次渲染需要花費(fèi)的時(shí)間:
可以看到,DOMContentLoaded 的時(shí)間的 3s+?,而花費(fèi)在 Rendering 上的就有整整 2900ms!
而如果給這個(gè)頁(yè)面的每個(gè)段落,添加上 content-visibility: auto,再看看整體的耗時(shí):
可以看到,DOMContentLoaded 的時(shí)間驟降至了 500ms+?,而花費(fèi)在 Rendering 上的,直接優(yōu)化到了 61ms!
2900ms --> 61ms,可謂是驚人級(jí)別的優(yōu)化了。因此,content-visibility: auto 對(duì)于長(zhǎng)文本、長(zhǎng)列表功能的優(yōu)化是顯而易見(jiàn)的。
利用 contain-intrinsic-size 解決滾動(dòng)條抖動(dòng)問(wèn)題
當(dāng)然,content-visibility 也存在一些小問(wèn)題。
從上面的例子,也能看到,在利用 content-visibility: auto 處理長(zhǎng)文本、長(zhǎng)列表的時(shí)候。在滾動(dòng)頁(yè)面的過(guò)程中,滾動(dòng)條一直在抖動(dòng),這不是一個(gè)很好的體驗(yàn)。
當(dāng)然,這也是許多虛擬列表都會(huì)存在的一些問(wèn)題。
好在,規(guī)范制定者也發(fā)現(xiàn)了這個(gè)問(wèn)題。這里我們可以使用另外一個(gè) CSS 屬性,也就是文章一開(kāi)頭提到的另外一個(gè)屬性 -- contain-intrinsic-size,來(lái)解決這個(gè)問(wèn)題。
contain-intrinsic-size?:控制由 content-visibility 指定的元素的自然大小。
什么意思呢?
還是上面的例子
<div class="g-wrap">
<div class="paragraph">...</div>
// ... 包含了 N 個(gè) paragraph
<div class="paragraph">...</div>
</div>
如果我們不使用 contain-intrinsic-size?,只對(duì)視口之外的元素使用 content-visibility: auto,那么視口外的元素高度通常就為 0。
當(dāng)然,如果直接給父元素設(shè)置固定的 height,也是會(huì)有高度的。
那么實(shí)際的滾動(dòng)效果,滾動(dòng)條就是抖動(dòng)的:
所以,我們可以同時(shí)利用上 contain-intrinsic-size?,如果能準(zhǔn)確知道設(shè)置了 content-visibility: auto 的元素在渲染狀態(tài)下的高度,就填寫(xiě)對(duì)應(yīng)的高度。如果如法準(zhǔn)確知道高度,也可以填寫(xiě)一個(gè)大概的值:
.paragraph {
content-visibility: auto;
contain-intrinsic-size: 320px;
}
如此之后,瀏覽器會(huì)給未被實(shí)際渲染的視口之外的 .paragraph 元素一個(gè)高度,避免出現(xiàn)滾動(dòng)條抖動(dòng)的現(xiàn)象:
你可以自己親自嘗試感受一下:CodePen Demo -- content-visibility: auto Demo[3]
content-visibility的一些其他問(wèn)題
首先,看看 content-visibility 的兼容性(2022-06-03):
目前還是比較慘淡的,并且我沒(méi)有實(shí)際在業(yè)務(wù)中使用它,需要再等待一段時(shí)間。當(dāng)然,由于該屬性屬于漸進(jìn)增強(qiáng)一類的功能,即便失效,也完全不影響頁(yè)面本身的展示。
同時(shí),也有一些同學(xué)表示,利用 content-visibility: auto 只能解決部分場(chǎng)景,在海量 DOM 的場(chǎng)景下的實(shí)際效果,還有待進(jìn)一步的實(shí)測(cè)。真正運(yùn)用的時(shí)候,多做對(duì)比,再做取舍。
當(dāng)然,現(xiàn)代瀏覽器已經(jīng)越來(lái)越智能,類似 content-visibility 功能的屬性也越來(lái)越多,我們?cè)谛阅軆?yōu)化的路上有了更多選擇,總歸是一件好事。
最后
本文到此結(jié)束,希望對(duì)你有幫助 :)
參考資料
[1]Youtube -- Slashing layout cost with content-visibility: https://www.youtube.com/watch?v=FFA-v-CIxJQ&t=869s。
[2]HTML - Living Standard: https://html.spec.whatwg.org/。
[3]CodePen Demo -- content-visibility: auto Demo: https://codepen.io/Chokcoco/pen/rNJvPEX。