探究 CSS 選擇器的性能真相
大家好,我是 CUGGZ。
在 CSS 中,有些選擇器會(huì)比其他選擇器執(zhí)行速度更快。下面就來(lái)深入研究 CSS 選擇器的性能真相,看看如何編寫 CSS 選擇器才能更快地執(zhí)行!
幕后
編寫 CSS 選擇器的方式會(huì)影響瀏覽器如何渲染頁(yè)面。每當(dāng)頁(yè)面發(fā)生變化時(shí),運(yùn)行它的瀏覽器引擎需要查看新的 DOM 樹,并確定如何根據(jù)可用的 CSS 樣式表對(duì)其進(jìn)行樣式設(shè)置。這種將樣式與 DOM 節(jié)點(diǎn)匹配的操作稱為重新計(jì)算樣式。瀏覽器引擎需要查看所有規(guī)則并決定哪些規(guī)則適用于給定元素。為此,引擎需要從右向左查看選擇器規(guī)則。
例如,當(dāng)引擎看到像.wrapper .section .title .link這樣的選擇器時(shí),它會(huì)首先嘗試將link類與元素匹配,如果匹配,則從右到左沿鏈向上找到類名為title的祖先元素,然后找到類名為section的元素,最后找到類名為wrapper的元素。
這個(gè)例子說(shuō)明,瀏覽器引擎只匹配 .link 可能比匹配更長(zhǎng)的 .wrapper .section .title .link 選擇器更快,因?yàn)樾枰獧z查的更少了。
當(dāng)然,類并不是在 CSS 選擇器中可以使用的唯一類型標(biāo)識(shí)符。一個(gè)有趣的例子就是使用屬性選擇器并進(jìn)行子字符串匹配,如 [class*="icon-"],這選擇器就要求瀏覽器引擎不僅要檢查元素是否有 class 屬性,還要檢查這個(gè)屬性的值是否包含子字符串 icon-。這個(gè)例子說(shuō)明,不同的選擇器編寫方式可能需要引擎或多或少的工作來(lái)應(yīng)用 CSS 規(guī)則。
在實(shí)踐中,這重要嗎?
這在很大程度上取決于網(wǎng)頁(yè)、DOM 樹的大小、CSS 規(guī)則的數(shù)量以及 DOM 是否經(jīng)常變化。不幸的是,并沒(méi)有關(guān)于這方面的規(guī)則。
事實(shí)上,談到規(guī)則,我們喜歡為什么是好的和什么是壞的制定規(guī)則。規(guī)則幫助我們快速做出決定,并在編寫代碼和設(shè)計(jì)軟件時(shí)提供指導(dǎo)。但這也會(huì)讓我們無(wú)法了解具體情況下真正發(fā)生的事。
在編寫 CSS 選擇器時(shí),嚴(yán)格應(yīng)用規(guī)則或使用 linter 自動(dòng)執(zhí)行,在某些情況下可能會(huì)適得其反。過(guò)于復(fù)雜的 CSS 選擇器,再加上變化很大的巨大 DOM 樹,很可能會(huì)導(dǎo)致性能不佳。過(guò)度的套用理論規(guī)則來(lái)獲得更好的性能,可能會(huì)使 CSS 更難閱讀和維護(hù),并且實(shí)際收益并不大。
因此,盡可能以對(duì)應(yīng)用有意義且易于閱讀和維護(hù)的方式來(lái)編寫代碼,然后再去衡量重要用戶場(chǎng)景的實(shí)際性能。
性能測(cè)量工具
Edge 瀏覽器中的 DevTools 提供了一個(gè)性能工具,它可以幫助我們測(cè)量頁(yè)面性能。在實(shí)際的測(cè)試中,我們要為用戶建立同理心,并盡可能使用他們實(shí)際使用的設(shè)備。因?yàn)橥_發(fā)機(jī)器可能比用戶的設(shè)備強(qiáng)大得多。DevTools 可以直接從工具內(nèi)部降低 CPU 和網(wǎng)絡(luò)連接速度。
從 Edge 109 版本開始,DevTools 中的性能工具可以列出樣式重新計(jì)算中成本最高的選擇器。使用方法如下:
- 打開 DevTools 中的性能工具;
- 點(diǎn)擊右上角的齒輪圖標(biāo)打開工具設(shè)置。
- 選中Enable advanced rendering instrumentation (slow)
- 點(diǎn)擊錄制按鈕,在要改進(jìn)的網(wǎng)頁(yè)上執(zhí)行特定場(chǎng)景,然后單擊停止;
- 在記錄的配置文件中,確定要改進(jìn)的重新計(jì)算樣式,并在瀑布視圖(“主要”部分)中選擇它;
- 在底部的選項(xiàng)卡欄中,點(diǎn)擊“選擇器統(tǒng)計(jì)信息”進(jìn)行查看。
DevTools 現(xiàn)在提供了瀏覽器引擎在此重新計(jì)算操作期間計(jì)算的所有 CSS 選擇器的列表,可以按選擇器處理時(shí)間或匹配次數(shù)對(duì)選擇器進(jìn)行排序。
如果發(fā)現(xiàn)一個(gè)選擇器需要很長(zhǎng)時(shí)間來(lái)處理,并且匹配了很多次,那么它可能就是一個(gè)可以優(yōu)化的對(duì)象。選擇器是否可以簡(jiǎn)化?是否可以更具體地描述需要匹配的元素?
案例分析
下面通過(guò)一個(gè)照片庫(kù)案例,看看如何改進(jìn) CSS 選擇器的性能!
這個(gè)頁(yè)面頂部有一個(gè)工具欄,可以按相機(jī)型號(hào)、光圈、曝光時(shí)間等過(guò)濾照片?,F(xiàn)在在相機(jī)型號(hào)之間切換感覺有點(diǎn)慢。所以,主要關(guān)注如下場(chǎng)景:
- 加載頁(yè)面,并等待過(guò)濾器準(zhǔn)備就緒;
- 將相機(jī)型號(hào)切換到另一個(gè)值并開始記錄性能;
- 切換回所有相機(jī)型號(hào)并停止錄制。
當(dāng)切換回所有相機(jī)型號(hào)時(shí)速度很慢,因此只需要測(cè)量這一過(guò)程。我們還將 CPU 速度降低四倍,以獲得比通常在功能強(qiáng)大的開發(fā)機(jī)器上獲得的結(jié)果更真實(shí)的結(jié)果。
錄制準(zhǔn)備就緒后,可以在配置文件中看到一個(gè)長(zhǎng)的重新樣式計(jì)算塊,總計(jì)執(zhí)行超過(guò) 900 毫秒。點(diǎn)擊這個(gè)塊,打開選擇器統(tǒng)計(jì)信息,然后按運(yùn)行時(shí)間排序:
一個(gè)選擇器需要匹配的工作越多,匹配的次數(shù)越多,通過(guò)改進(jìn)這個(gè)選擇器獲得的性能提升就越多。在列表中,主要關(guān)注以下選擇器:
- .gallery .photo .meta ::selection
- .gallery .photo .meta li strong:empty
- [class*=" gallery-icon--"]::before
- .gallery .photo .meta li
- *
- html[dir="rtl"] .gallery .photo .meta li button
改進(jìn) ::selection 選擇器
.gallery.photo.meta ::selection 選擇器用來(lái)匹配照片元數(shù)據(jù)被用戶選中時(shí)的背景和文本顏色。當(dāng)用戶選擇照片下方的文本時(shí),將使用自定義顏色而不是瀏覽器默認(rèn)顏色。
由于代碼中的錯(cuò)誤,這種特殊情況實(shí)際上是有問(wèn)題的。真正的代碼應(yīng)該是 .gallery.photo.meta::selection,即::selection前面沒(méi)有空格。因?yàn)檫@個(gè)錯(cuò)誤的空格,選擇器實(shí)際上被引擎解析為 .gallery .photo .meta *::selection,這使得在樣式重新計(jì)算期間匹配起來(lái)要慢得多,因?yàn)橐嫘枰獧z查所有 DOM 元素,然后驗(yàn)證它們是否嵌套在正確的祖先中。
如果沒(méi)有多余的空格,引擎只需要檢查元素是否具有.meta類,然后再繼續(xù)即可。
改進(jìn) :empty 選擇器
.gallery .photo .meta li strong:empty中的 :empty 選擇器表示僅在 strong 元素沒(méi)有任何內(nèi)容時(shí)匹配。這就可能需要引擎做更多的工作,而不僅僅是檢查元素的標(biāo)簽名稱。
查看與此類似的其他 CSS 規(guī)則,可以看到:
相同的選擇器重復(fù)兩次,但第二個(gè)選擇器以 html[dir=rtl] 為前綴,當(dāng)頁(yè)面上的文本方向是從右到左時(shí),rtl 方向規(guī)則會(huì)覆蓋左邊距并將其替換為右邊距。
為了改進(jìn)這一點(diǎn),可以使用 CSS 邏輯屬性。可以使用符合任何文本方向的邏輯邊距方向,而不是指定物理邊距方向:
這樣,第二個(gè)選擇器 html[dir="rtl"] .gallery .photo .meta li button 就可以去掉了。
改進(jìn) [class*="gallery-icon--"] 選擇器
以下是使用此選擇器的 CSS 規(guī)則:
這里可以通過(guò)圖標(biāo)類給元素添加對(duì)應(yīng)的圖標(biāo)。這就要求引擎讀取類名并對(duì)其進(jìn)行子字符串搜索。可以通過(guò)以下方式幫助引擎減少工作量:
現(xiàn)在,不只使用一個(gè)類,而是向元素添加兩個(gè)類:<div class="gallery-icon camera">,而不是 <div class="gallery-icon--camera">,這樣就無(wú)需再讀取類名以及搜索子字符串,減少了瀏覽器引擎的工作量。
改進(jìn) .gallery .photo .meta li 選擇器
這個(gè)選擇器會(huì)強(qiáng)制瀏覽器去檢查 li 元素的祖先列表中的多個(gè)層級(jí)。案例的網(wǎng)頁(yè)中有很多 li 元素,這可能意味著很多工作。
可以通過(guò)為 li 元素指定一個(gè)特定的類并刪除不必要的嵌套來(lái)簡(jiǎn)化它:
改進(jìn) * 選擇器
*符號(hào)在 CSS 中用作匹配任何元素的通用選擇器。這種匹配任何內(nèi)容的能力意味著引擎需要將相關(guān)規(guī)則應(yīng)用于所有元素。在性能記錄中可以看到,這個(gè)選擇器被匹配了很多次。
在案例中,它應(yīng)用了一個(gè)特定的 box-sizing 值:
這在 CSS 中很常見,但最好刪除它,僅在需要時(shí)設(shè)置 box-sizing 屬性。
改進(jìn)結(jié)果
完成以上改進(jìn)之后,再來(lái)查看這個(gè)場(chǎng)景的性能情況:
在第一次性能記錄中,同樣的“重新計(jì)算樣式”塊運(yùn)行時(shí)間幾乎為一秒,現(xiàn)在運(yùn)行時(shí)間約為 300 毫秒,性能獲得了很大的提升!
總結(jié)
案例研究表明,改進(jìn)某些 CSS 選擇器可以帶來(lái)重要的性能提升。然而,關(guān)鍵是要記住,這主要取決于特定用例??梢允褂眯阅芄ぞ邅?lái)測(cè)試網(wǎng)頁(yè)的性能,如果發(fā)現(xiàn)樣式重新計(jì)算使渲染變慢,就使用 Edge 瀏覽器中新的“選擇器統(tǒng)計(jì)信息”功能!
參考:https://blogs.windows.com/msedgedev/2023/01/17/the-truth-about-css-selector-performance/