前端(vue)入門到精通課程:進(jìn)入學(xué)習(xí)
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API調(diào)試工具:點(diǎn)擊使用
對(duì)于前端人員來講,最令人頭疼的應(yīng)該就是頁(yè)面性能了,當(dāng)用戶在訪問一個(gè)頁(yè)面時(shí),總是希望它能夠快速呈現(xiàn)在眼前并且是可交互狀態(tài)。如果頁(yè)面加載過慢,你的用戶很可能會(huì)因此離你而去。所以頁(yè)面性能對(duì)于前端開發(fā)者來說可謂是重中之重,其實(shí)你如果了解頁(yè)面從加載到渲染完成的整個(gè)過程,就知道應(yīng)該從哪方面下手了。
嗯,不要跑偏了,今天我們主要來研究長(zhǎng)列表頁(yè)面的渲染性能
現(xiàn)如今的頁(yè)面越來越復(fù)雜,一個(gè)頁(yè)面往往承載著大量的元素,最常見的就是一些電商頁(yè)面,數(shù)以萬計(jì)的商品列表是怎么保證渲染不卡頓的,大家在面對(duì)這種長(zhǎng)列表渲染的場(chǎng)景下,一般都會(huì)采用分頁(yè)或者虛擬列表來減緩頁(yè)面一次性渲染的壓力,但這些方式都需要配合JS來時(shí)實(shí)現(xiàn),那么有沒有僅使用CSS就能夠?qū)崿F(xiàn)的方案呢?
答案是有的,它就是我們今天的主角 —— 內(nèi)容可見性(content-visibility)?!就扑]學(xué)習(xí):css視頻教程】
content-visibility
屬性值
content-visibility
是CSS新增的屬性,主要用來提高頁(yè)面渲染性能,它可以控制一個(gè)元素是否渲染其內(nèi)容,并且允許瀏覽器跳過這些元素的布局與渲染。
- visible:默認(rèn)值,沒有效果。元素的內(nèi)容被正常布局和呈現(xiàn)。
- hidden:元素跳過它的內(nèi)容。跳過的內(nèi)容不能被用戶代理功能訪問,例如在頁(yè)面中查找、標(biāo)簽順序?qū)Ш降?,也不能被選擇或聚焦。這類似于給內(nèi)容設(shè)置
display: none
。 - auto:該元素打開布局包含、樣式包含和繪制包含。如果該元素與用戶不相關(guān),它也會(huì)跳過其內(nèi)容。與 hidden 不同,跳過的內(nèi)容必須仍可正常用于用戶代理功能,例如在頁(yè)面中查找、tab 順序?qū)Ш降?,并且必須正??删劢购涂蛇x擇。
content-visibility: hidden手動(dòng)管理可見性
上面說到content-visibility: hidden
的效果與display: none
類似,但其實(shí)兩者還是有比較大的區(qū)別的:
- content-visibility: hidden 只是隱藏了子元素,自身不會(huì)被隱藏
- content-visibility: hidden 隱藏內(nèi)容的渲染狀態(tài)會(huì)被緩存,所以當(dāng)它被移除或者設(shè)為可見時(shí),瀏覽器不會(huì)重新渲染,而是會(huì)應(yīng)用緩存,所以對(duì)于需要頻繁切換顯示隱藏的元素,這個(gè)屬性能夠極大地提高渲染性能。
從這上面我們可以看到,添加了content-visibility: hidden
元素的子元素確實(shí)是沒有渲染,但它自身是會(huì)渲染的!
content-visibility: auto 跳過渲染工作
我們仔細(xì)想想,頁(yè)面上雖然會(huì)有很多元素,但是它們會(huì)同時(shí)呈現(xiàn)在用戶眼前嗎,很顯然是不會(huì)的,用戶每次能夠真實(shí)看到就只有設(shè)備可見區(qū)那些內(nèi)容,對(duì)于非可見區(qū)的內(nèi)容只要頁(yè)面不發(fā)生滾動(dòng),用戶就永遠(yuǎn)看不到。雖然用戶看不到,但瀏覽器卻會(huì)實(shí)實(shí)在在的去渲染,以至于浪費(fèi)大量的性能。所以我們得想辦法讓瀏覽器不渲染非可視區(qū)的內(nèi)容就能夠達(dá)到提高頁(yè)面渲染性能的效果。
我們上面說到的虛擬列表原理其實(shí)就跟這個(gè)類似,在首屏加載時(shí),只加載可視區(qū)
的內(nèi)容,當(dāng)頁(yè)面發(fā)生滾動(dòng)時(shí),動(dòng)態(tài)通過計(jì)算獲得可視區(qū)
的內(nèi)容,并將非可視區(qū)
的內(nèi)容進(jìn)行刪除,這樣就能夠大大提高長(zhǎng)列表的渲染性能。
但這個(gè)需要配合JS才能實(shí)現(xiàn),現(xiàn)在我們可以使用CSS中content-visibility: auto
,它可以用來跳過屏幕外的內(nèi)容渲染,對(duì)于這種有大量離屏內(nèi)容的長(zhǎng)列表,可以大大減少頁(yè)面渲染時(shí)間。
我們將上面的例子稍微改改:
<template> <div> <div> <img :src="book.bookCover" /> <div> <div>{{ `${book.bookName}${index + 1}` }}</div> <div>{{ book.catlog }}</div> <div> <div v-for="(item, index) in book.tags" :key="index"> {{ item }} </div> </div> <div> {{ book.desc }} </div> </div> </div> </div> </template> <script setup> import { toRefs } from "vue"; const props = defineProps<{ book: any; index: any; }>(); const { book, index } = toRefs(props); </script> <style scoped> .card_item { margin: 20px auto; content-visibility: auto; } / * ... */ </style>
首先是沒有添加content-visibility: auto
的效果,無論這些元素是否在可視區(qū),都會(huì)被渲染
如果我們?cè)谄匠I(yè)務(wù)中這樣寫,用戶進(jìn)入到這個(gè)頁(yè)面可能就直接口吐芬芳了,為了性能考慮,我們?yōu)槊恳粋€(gè)列表項(xiàng)加上:
.card_item { content-visibility: auto; }
這個(gè)時(shí)候我們?cè)賮砜聪滦Ч?/p>
從第10個(gè)開始,這些沒在可視區(qū)的元素就沒有被渲染,這可比上面那種全部元素都渲染好太多了,但是如果瀏覽器不渲染頁(yè)面內(nèi)的一些元素,滾動(dòng)將是一場(chǎng)噩夢(mèng),因?yàn)闊o法正確計(jì)算頁(yè)面高度。這是因?yàn)椋?code>content-visibility會(huì)將分配給它的元素的高度(height
)視為0
,瀏覽器在渲染之前會(huì)將這個(gè)元素的高度變?yōu)?code>0,從而使我們的頁(yè)面高度和滾動(dòng)變得混亂。
這里我們可以看到頁(yè)面上的滾動(dòng)條會(huì)出現(xiàn)抖動(dòng)現(xiàn)象,這是因?yàn)榭梢晠^(qū)外的元素只有出現(xiàn)在了可視區(qū)才會(huì)被渲染,這就回導(dǎo)致前后頁(yè)面高度會(huì)發(fā)生變化,從而出現(xiàn)滾動(dòng)條的詭異抖動(dòng)現(xiàn)象,這是虛擬列表基本都會(huì)存在的問題。
⚠️注意:當(dāng)元素接近視口時(shí),瀏覽器不再添加size
容器并開始繪制和命中測(cè)試元素的內(nèi)容。這使得渲染工作能夠及時(shí)完成以供用戶查看。
這也是為什么上面我們看到的是從第十個(gè)才開始不渲染子元素,因?yàn)樗枰粋€(gè)緩沖區(qū)以便瀏覽器能夠在頁(yè)面發(fā)生滾動(dòng)時(shí)及時(shí)渲染呈現(xiàn)在用戶眼前。
上面提到的size
其實(shí)是一種 CSS 屬性的潛在值contain
,它指的是元素上的大小限制確保元素的框可以在不需要檢查其后代的情況下進(jìn)行布局。這意味著如果我們只需要元素的大小,我們可以跳過后代的布局。
contain-intrinsic-size 救場(chǎng)
頁(yè)面在滾動(dòng)過程中滾動(dòng)條一直抖動(dòng),這是一個(gè)不能接受的體驗(yàn)問題,為了更好地實(shí)現(xiàn)content-visibility
,瀏覽器需要應(yīng)用 size containment 以確保內(nèi)容的渲染結(jié)果不會(huì)以任何方式影響元素的大小。這意味著該元素將像空的一樣布局。如果元素沒有在常規(guī)塊布局中指定的高度,那么它將是 0 高度。
這個(gè)時(shí)候我們可以使用contain-intrinsic-size
來指定的元素自然大小,確保我們未渲染子元素的 div 仍然占據(jù)空間,同時(shí)也保留延遲渲染的好處。
語(yǔ)法
此屬性是以下 CSS 屬性的簡(jiǎn)寫:
contain-intrinsic-width
contain-intrinsic-height
/* Keyword values */ contain-intrinsic-width: none; /* <length> values */ contain-intrinsic-size: 1000px; contain-intrinsic-size: 10rem; /* width | height */ contain-intrinsic-size: 1000px 1.5em; /* auto <length> */ contain-intrinsic-size: auto 300px; /* auto width | auto height */ contain-intrinsic-size: auto 300px auto 4rem;
contain-intrinsic-size 可以為元素指定以下一個(gè)或兩個(gè)值。如果指定了兩個(gè)值,則第一個(gè)值適用于寬度,第二個(gè)值適用于高度。如果指定單個(gè)值,則它適用于寬度和高度。
實(shí)現(xiàn)
我們只需要給添加了content-visibility: auto
的元素添加上contain-intrinsic-size
就能夠解決滾動(dòng)條抖動(dòng)的問題,當(dāng)然,這個(gè)高度約接近真實(shí)渲染的高度,效果會(huì)越好,如果實(shí)在無法知道準(zhǔn)確的高度,我們也可以給一個(gè)大概的值,也會(huì)使?jié)L動(dòng)條的問題相對(duì)減少。
.card_item { content-visibility: auto; contain-intrinsic-size: 200px; }
之前沒添加contain-intrinsic-size
屬性時(shí),可視區(qū)外的元素高度都是0,現(xiàn)在這些元素高度都是我們?cè)O(shè)置的contain-intrinsic-size
的值,這樣的話整個(gè)頁(yè)面的高度就是不會(huì)發(fā)生變化(或者說變化很?。?,從而頁(yè)面滾動(dòng)條也不會(huì)出現(xiàn)抖動(dòng)問題(或者說抖動(dòng)減少)
性能對(duì)比
上面說了這么多,content-visibility
是否真的能夠提高頁(yè)面的渲染性能呢,我們來實(shí)際對(duì)比看看:
- 首先是沒有
content-visibility
的頁(yè)面渲染
- 然后是有
content-visibility
的頁(yè)面渲染
上面是用1000個(gè)列表元素進(jìn)行測(cè)試的,有content-visibility
的頁(yè)面渲染花費(fèi)時(shí)間大概是37ms,而沒有content-visibility
的頁(yè)面渲染花費(fèi)時(shí)間大概是269ms,提升了足足有7倍之多?。。?/p>
對(duì)于列表元素