重構 CSS:優化大小和性能(第 3 部分)
已發表: 2022-03-10在本系列的前幾篇文章中,我們介紹了審核 CSS 代碼庫運行狀況以及增量 CSS 重構策略、測試和維護。 無論在重構過程中 CSS 代碼庫改進了多少,以及它的可維護性和可擴展性如何,最終的樣式表都需要優化以獲得最佳性能和最小文件大小。
部署重構的代碼庫不應導致更差的網站性能和更差的用戶體驗。 畢竟,用戶不會永遠等待網站加載。 此外,儘管代碼質量有所提高,但管理層將對未優化的代碼庫導致的流量和收入下降感到不滿。
在本文中,我們將介紹可以優化 CSS 文件大小、加載時間和渲染性能的CSS 優化策略。 這樣,重構後的 CSS 代碼庫不僅更易於維護和擴展,而且性能更高,並且可以選中所有對最終用戶和管理人員都很重要的框。
部分:CSS 重構
- 第 1 部分:CSS 重構:簡介
- 第 2 部分:CSS 策略、回歸測試和維護
- 第 3 部分:優化大小和性能
- 訂閱我們的電子郵件通訊,不要錯過下一個。
優化樣式表文件大小
優化文件大小歸結為刪除不必要的字符和格式化並優化 CSS 代碼以使用不同的語法或速記屬性來減少文件中的字符總數。
優化和縮小
CSS 優化和縮小已經存在多年,並成為前端優化的主要內容。 在 CSS 優化和縮小方面,cssnano 和 clean-css 等工具是我最喜歡的工具之一。 它們提供了多種自定義選項,以進一步控制如何優化代碼以及支持哪些瀏覽器。
這些工具以類似的方式工作。 首先,未優化的代碼按照配置中設置的規則進行解析和轉譯。 結果是使用較少字符但仍保留格式(換行符和空格)的代碼。
/* Before - original and unoptimized code */ .container { padding: 24px 16px 24px 16px; background: #222222; } /* After - optimized code with formatting */ .container { padding: 24px 16px; background: #222; }
最後,通過刪除所有不必要的文本格式來縮小轉譯的優化代碼。 根據配置中設置的代碼庫和支持的瀏覽器,帶有不推薦使用的供應商前綴的代碼也可以被刪除。
/* Before - optimized code with formatting */ .container { padding: 24px 16px; background: #222; } /* After - optimized and minified code */ .container{padding:24px 16px;background:#222}
即使在這個基本示例中,我們也設法將整體文件大小從 76 字節減少到 55 字節,從而減少了 23%。 根據代碼庫和優化工具和配置,CSS 優化和縮小可能會更加有效。
由於只需對 CSS 工作流程進行一些調整即可獲得巨大的回報,因此 CSS 優化和縮小可以被認為是輕鬆的勝利。 這就是為什麼縮小應該被視為最低限度的性能優化和項目中所有樣式表的要求。
優化媒體查詢
當我們用 CSS 編寫媒體查詢時,尤其是在使用多個文件(PostCSS 或 Sass)時,我們通常不會將代碼嵌套在整個項目的單個媒體查詢下。 為了提高可維護性、模塊化和代碼結構,我們通常為多個 CSS 組件編寫相同的媒體查詢表達式。
讓我們考慮以下未優化 CSS 代碼庫的示例。
.page { display: grid; grid-gap: 16px; } @media (min-width: 768px) { .page { grid-template-columns: 268px auto; grid-gap: 24px; } } /* ... */ .products-grid { display: grid; grid-template-columns: repeat(2, 1fr); grid-gap: 16px; } @media (min-width: 768px) { .products-grid { grid-template-columns: repeat(3, 1fr); grid-gap: 20px; } }
如您所見,我們為每個組件重複了@media (min-width: 768px)
,以提高可讀性和維護性。 讓我們在這個代碼示例上運行優化和縮小,看看我們得到了什麼。
.page{display:grid;grid-gap:16px}@media (min-width: 768px){.page{grid-template-columns:268px auto;grid-gap:24px}}.products-grid{display:grid;grid-template-columns:repeat(2,1fr);grid-gap:16px}@media (min-width: 768px){.products-grid{grid-template-columns:repeat(3,1fr);grid-gap:20px}}
這可能有點難以閱讀,但我們只需要注意重複的@media (min-width: 768px)
媒體查詢。 我們已經得出結論,我們想要減少樣式表中的字符數,並且可以在單個媒體查詢下嵌套多個選擇器,那麼為什麼壓縮器不刪除重複的表達式呢? 原因很簡單。
CSS 中的規則順序很重要,因此要合併重複的媒體查詢,需要移動代碼塊。 這將導致規則順序被更改,這可能會導致樣式中出現不必要的副作用。
但是,結合媒體查詢可能會使文件大小更小,具體取決於代碼庫和結構。 postcss-sort-media-queries 等工具和包允許我們刪除重複的媒體查詢並進一步減小文件大小。
當然,有一個重要的警告是要有一個結構良好的 CSS 代碼庫結構,它不依賴於規則順序。 在規劃 CSS 重構和建立基本規則時,應該考慮到這種優化。
我建議首先檢查優化收益是否超過潛在風險。 這可以通過運行 CSS 審核和檢查媒體查詢統計信息輕鬆完成。 如果是這樣,我建議稍後添加它並運行自動回歸測試以捕捉任何意外的副作用和可能因此發生的錯誤。
刪除未使用的 CSS
在重構過程中,您總是有可能最終得到一些尚未完全刪除的未使用的遺留樣式,或者您將有一些未使用的新添加樣式。 這些樣式還增加了總字符數和文件大小。 然而,使用自動化工具消除這些未使用的樣式可能會有些風險,因為這些工具無法準確預測實際使用的樣式。
像 purgecss 這樣的工具會遍歷項目中的所有文件,並將文件中提到的所有類用作選擇器,只是為了謹慎起見,不要意外刪除動態、注入 JavaScript 元素的選擇器以及其他潛在情況。 但是,purgecss 提供了靈活的配置選項作為這些潛在問題和風險的解決方法。
但是,只有在潛在收益大於風險時才應該進行這種改進。 此外,這種優化技術將需要相當長的時間來設置、配置和測試,並且可能會導致意想不到的問題,因此請謹慎操作並確保設置是防彈的。
消除渲染阻塞 CSS
默認情況下,CSS 是一種阻止渲染的資源,這意味著在瀏覽器下載並解析所有鏈接的樣式表及其依賴項(例如字體)之前,網站不會顯示給用戶。
如果樣式表文件具有較大的文件大小或位於第三方服務器或 CDN 上的多個依賴項,則網站渲染可能會因網絡速度和可靠性而顯著延遲。
在過去的幾個月中,最大內容塗料(LCP) 已成為一項重要指標。 LCP 不僅對性能很重要,而且對 SEO 也很重要——LCP 得分越高的網站搜索結果排名也會越好。 刪除像 CSS 這樣的渲染阻塞資源是提高 LCP 分數的一種方法。
但是,如果我們推遲樣式表的加載和處理,這將導致Flash Of Unstyled Content (FOUC)——內容會立即顯示給用戶,並且稍後會加載和應用樣式。 這個開關可能看起來很刺耳,甚至可能會讓一些用戶感到困惑。
關鍵 CSS
使用關鍵 CSS,我們可以確保網站以最少的樣式加載,這些樣式保證在最初呈現時在頁面上使用。 這樣,我們可以使 FOUC 變得不那麼引人注目,甚至在大多數情況下消除它。 例如,如果主頁具有帶導航的標題組件和位於首屏的英雄組件,這意味著關鍵 CSS 將包含這些組件的所有必要的全局和組件樣式,而頁面上其他組件的樣式將被推遲。
該 CSS 在 HTML 的style
標記下內聯,因此樣式與 HTML 文件一起加載和解析。 雖然這會導致HTML 文件大小稍大(也應該縮小),但所有其他非關鍵 CSS 將被延遲並且不會立即加載,並且網站將呈現更快。 總而言之,好處超過了 HTML 文件大小的增加。
<head> <style type="text/css"><!-- Minified Critical CSS markup --></style> </head>
根據您的設置,有許多自動化工具和 NPM 包可以提取關鍵 CSS 並生成延遲樣式表。
延遲樣式表
我們究竟如何使 CSS 成為非阻塞的? 我們知道,在第一次下載頁面 HTML 時,不應在 HTML head
元素中引用它。 Demian Renzulli 在他的文章中概述了這種方法。
目前還沒有原生 HTML 方法來優化或延遲渲染阻塞資源的加載,因此我們需要在初始渲染後使用 JavaScript 將非關鍵樣式表插入 HTML 標記中。 如果用戶在瀏覽器中未啟用 JavaScript 訪問頁面,我們還需要確保這些樣式以非最佳(渲染阻止)方式加載。
<!-- Deferred stylesheet --> <link rel="preload" as="style" href="path/to/stylesheet.css" onload="this.onload=null;this.rel='stylesheet'"> <!-- Fallback --> <noscript> <link rel="stylesheet" href="path/to/stylesheet.css"> </noscript>
使用link rel="preload" as="style"
確保樣式表文件被異步請求,而onload
JavaScript 處理程序確保文件在 HTML 文檔完成加載後由瀏覽器加載和處理。 需要進行一些清理,因此我們需要將onload
設置為null
以避免此函數多次運行並導致不必要的重新渲染。
這正是 Smashing Magazine 處理其樣式表的方式。 每個模板(主頁、文章類別、文章頁面等)都有一個模板特定的關鍵 CSS內聯在head
元素的 HTML style
標記內,以及一個包含所有非關鍵樣式的延遲main.css
樣式表。
然而,在這裡我們可以看到媒體查詢從自動延遲的低優先級print
媒體切換到高優先級的all
屬性,而不是切換rel
參數,當頁面完成加載時。 這是延遲加載非關鍵樣式表的另一種同樣可行的方法。
<link href="/css/main.css" media="print" onload="this.media='all'" rel="stylesheet">
使用媒體查詢拆分和有條件地加載樣式表
對於即使在應用上述優化後最終樣式表文件仍具有較大文件大小的情況,您可以根據媒體查詢將樣式表拆分為多個文件,並在鏈接 HTML 元素中引用的樣式表上使用 media 屬性有條件地加載它們.
<link href="print.css" rel="stylesheet" media="print"> <link href="mobile.css" rel="stylesheet" media="all"> <link href="tablet.css" rel="stylesheet" media="screen and (min-width: 768px)"> <link href="desktop.css" rel="stylesheet" media="screen and (min-width: 1366px)">
這樣,如果使用移動優先的方法,則不會在可能運行在較慢或不可靠網絡上的移動設備上下載或解析較大屏幕尺寸的樣式。
重申一下,如果前面提到的優化方法的結果導致樣式表的文件大小不理想,則應使用此方法。 對於常規情況,這種優化方法不會那麼有效或有影響力,具體取決於單個樣式表的大小。
推遲字體文件和样式表
延遲字體樣式表(例如 Google 字體文件)也可能有利於初始渲染性能。 我們已經得出結論,樣式表是渲染阻塞的,但樣式表中引用的字體文件也是如此。 字體文件也會給初始渲染性能增加相當多的開銷。
加載字體樣式表和字體文件是一個複雜的話題,深入研究它需要一篇全新的文章來解釋所有可行的方法。 幸運的是,Zach Leatherman 在這本很棒的綜合指南中概述了許多可行的策略,並總結了每種方法的優缺點。 如果您使用 Google 字體,Harry Roberts 概述了最快加載 Google 字體的策略。
如果您決定推遲字體樣式表,您最終會得到 Flash of Unstyled Text (FOUT)。 該頁面最初將使用備用字體呈現,直到下載並解析了延遲字體文件和样式表,此時將應用新樣式。 這種變化可能非常明顯,可能會導致佈局變化並使用戶感到困惑,具體取決於具體情況。
Barry Pollard 概述了一些可以幫助我們處理 FOUT 的策略,並談到了即將推出的尺寸調整 CSS 功能,它將提供一種更簡單、更原生的處理 FOUT 的方式。
服務器端優化
HTTP 壓縮
除了縮小和文件大小優化之外,HTML、CSS 文件、JavaScript 文件等靜態資源。Gzip 和 Brotli 等 HTTP 壓縮算法可用於額外減小下載文件的大小。
需要在服務器上配置 HTTP 壓縮,這取決於技術堆棧和配置。 但是,性能優勢可能會有所不同,並且可能沒有標準樣式表縮小和優化那麼大的影響,因為瀏覽器仍將解壓縮壓縮文件並必須解析它們。
緩存樣式表
緩存靜態文件是一種有用的優化策略。 瀏覽器仍然需要在第一次加載時從服務器下載靜態文件,但是一旦它們被緩存,它們將在後續請求中直接從服務器加載,從而加快加載過程。
可以通過服務器級別的 Cache-Control HTTP 標頭來控制緩存(例如,使用 Apache 服務器上的.htaccess
文件)。
使用max-age
我們可以指示文件應該在瀏覽器中緩存多長時間(以秒為單位),使用public
,我們指示文件可以被瀏覽器和任何其他緩存緩存。
Cache-Control: public, max-age=604800
使用immutable
配置可以實現更積極有效的靜態資產緩存策略。 這告訴瀏覽器這個特定的文件永遠不會改變,任何新的更新都將導致這個文件被刪除,一個具有不同文件名的新文件將取代它。 這稱為緩存清除。
Cache-Control: public, max-age=604800, immutable
如果沒有適當的緩存清除策略,就有失去對緩存在用戶瀏覽器上的文件的控制的風險。 這意味著如果文件要更改,瀏覽器將無法知道它應該下載更新的文件而不使用過時的緩存文件。 從那時起,我們幾乎無法解決這個問題,用戶將被過時的文件卡住,直到它過期。
對於樣式表,這可能意味著如果我們用需要新樣式的新內容和組件更新 HTML 文件,這些樣式將不會顯示,因為過時的樣式表在沒有緩存清除策略的情況下被緩存,瀏覽器不會知道這一點它必須下載新文件。
在對樣式表或任何其他靜態文件使用緩存策略之前,應實施有效的緩存清除機制以防止過時的靜態文件卡在用戶的緩存中。 您可以使用以下版本控制機制之一進行緩存清除:
- 將查詢字符串附加到文件名。
例如styles.css?v=1.0.1.
但是,某些 CDN 可以完全忽略或從文件名中去除查詢字符串,從而導致文件卡在用戶的緩存中並且永遠不會更新。 - 更改文件名或附加哈希。
例如styles.a1bc2.css
或styles.v1.0.1.css.
這比將查詢字符串附加到文件名更可靠和有效。
CDN 還是自託管?
內容交付網絡 (CDN) 是一組地理分佈的服務器,通常用於可靠和快速地交付靜態資產,如圖像、視頻、HTML 文件、CSS 文件、JavaScript 文件等。
儘管 CDN 似乎是自託管靜態資產的絕佳替代品,但 Harry Roberts 對該主題進行了深入研究,並得出結論認為自託管資產對性能更有利。
“真的沒有什麼理由讓你的靜態資產留在其他人的基礎設施上。 感知到的好處通常是一個神話,即使它們不是,取捨也根本不值得。 從多個來源加載資產的速度明顯較慢。”
話雖如此,我建議默認情況下自託管樣式表(包括字體樣式表,如果可能),並且僅在有可行的理由或其他好處的情況下才遷移到 CDN。
審核 CSS 文件大小和性能
WebPageTest 和其他類似的性能審計工具可用於詳細了解網站加載過程、文件大小、渲染阻止資源等。這些工具可以讓您深入了解您的網站如何在各種設備上加載 -從在高速網絡上運行的台式電腦到在慢速和不可靠網絡上運行的低端智能手機。
讓我們對本系列第一篇文章中提到的網站進行性能審計 - 具有 2MB 縮小 CSS 的網站。
首先,我們將查看內容細分以確定哪些資源佔用的帶寬最多。 從下面的圖表中,我們可以看到圖像佔用了大部分請求,這意味著它們需要延遲加載。 從第二個圖表中,我們可以看到樣式表和 JavaScript 文件的文件大小是最大的。 這很好地表明這些文件需要被縮小和優化、重構或拆分為多個文件並異步加載。
我們可以從 Web Vitals 圖表中得出更多結論。 通過查看最大內容繪製 (LCP) 圖表,我們可以詳細了解渲染阻塞資源以及它們對初始渲染的影響程度。
我們已經可以得出結論,網站樣式表將對 LCP 和加載統計數據產生最大影響。 但是,我們可以看到樣式表中引用的字體樣式表、JavaScript 文件和圖像,它們也是呈現阻塞的。 知道我們可以應用上述優化方法通過消除渲染阻塞資源來減少 LCP 時間。
結論
當代碼的健康和質量得到改善以及代碼庫的弱點和問題得到修復時,重構過程還沒有完成。 與遺留代碼庫相比,重構的代碼庫應該產生相同或改進的性能。
最終用戶不應遇到重構代碼庫的性能問題或加載時間過長。 幸運的是,有許多方法可以確保代碼庫既健壯又高效——從簡單的縮小和優化方法到更複雜的方法,如消除渲染阻塞資源和代碼拆分。
我們可以使用WebPageTest等各種性能審計工具來詳細了解加載時間、性能、渲染阻塞資源和其他因素,以便我們能夠及早有效地解決這些問題。
部分:CSS 重構
- 第 1 部分:CSS 重構:簡介
- 第 2 部分:CSS 重構:策略、回歸測試和維護
- 第 3 部分: CSS 重構:優化大小和性能
- 訂閱我們的電子郵件通訊,不要錯過下一個。
參考
- “渲染阻塞 CSS”,Ilya Grigorik
- “推遲非關鍵 CSS”,Demian Renzulli
- “字體加載策略綜合指南”,Zach Leatherman
- “減少字體加載影響的新方法:CSS 字體描述符”,Barry Pollard
- “自行託管您的靜態資產”,Harry Roberts
- “優化 WebFont 加載和渲染”,Ilya Grigorik