CSS性能優化的8個技巧

咱們都知道對於網站來講,性能相當重要,CSS 做爲頁面渲染和內容展示的重要環節,影響着用戶對整個網站的第一體驗。所以,與其相關的性能優化是不容忽視的。css

對於性能優化咱們經常在項目完成時纔去考慮,常常被推遲到項目的末期,甚至到暴露出嚴重的性能問題時才進行性能優化,相信大多數人對此深有體會。html

筆者認爲,爲了更多地避免這一狀況,首先要重視起性能優化相關的工做,將其貫穿到整個產品設計與開發中。其次,就是了解性能相關的內容,在項目開發過程當中,天然而然地進行性能優化。最後,也是最最重要的,那就是從如今開始實施優化。html5

推薦你們閱讀下奇舞週刊以前推的《嗨,送你一張 Web 性能優化地圖》1這篇文章,可以幫助你們對性能優化須要作的事以及須要考慮的問題造成一個總體的概念。webpack

本文將會詳細介紹 CSS 性能優化相關的技巧,筆者將它們分爲實踐型建議型兩類,共 8 個小技巧。實踐型技巧可以快速地應用在項目中,可以很好地提高性能,也是筆者常用的,建議你們儘快在項目中實踐。建議型技巧中,有的可能對性能影響並不顯著,有的平時你們也並不會那麼用,因此筆者不會着重講述,讀者們能夠根據自身狀況瞭解一下便可。git

在正式開始以前,須要你們對於瀏覽器的工做原理2有些必定的瞭解,須要的小夥伴能夠先簡單瞭解下。github


下面咱們開始介紹實踐型的 4 個優化技巧,先從首屏關鍵 CSS 開始。web

1. 內聯首屏關鍵 CSS(Critical CSS)

性能優化中有一個重要的指標——首次有效繪製(First Meaningful Paint,簡稱 FMP)即指頁面的首要內容(primary content)出如今屏幕上的時間。這一指標影響用戶看到頁面前所需等待的時間,而**內聯首屏關鍵 CSS(即 Critical CSS,能夠稱之爲首屏關鍵 CSS)**能減小這一時間。gulp

你們應該都習慣於經過 link 標籤引用外部 CSS 文件。但須要知道的是,將 CSS 直接內聯到 HTML 文檔中能使 CSS 更快速地下載。而使用外部 CSS 文件時,須要在 HTML 文檔下載完成後才知道所要引用的 CSS 文件,而後才下載它們。因此說,內聯 CSS 可以使瀏覽器開始頁面渲染的時間提早,由於在 HTML 下載完成以後就能渲染了。瀏覽器

既然內聯 CSS 可以使頁面渲染的開始時間提早,那麼是否能夠內聯全部的 CSS 呢?答案顯然是否認的,這種方式並不適用於內聯較大的 CSS 文件。由於初始擁塞窗口3存在限制(TCP 相關概念,一般是 14.6kB,壓縮後大小),若是內聯 CSS 後的文件超出了這一限制,系統就須要在服務器和瀏覽器之間進行更屢次的往返,這樣並不能提早頁面渲染時間。所以,咱們應當只將渲染首屏內容所需的關鍵 CSS 內聯到 HTML 中緩存

既然已經知道內聯首屏關鍵 CSS 可以優化性能了,那下一步就是如何肯定首屏關鍵 CSS 了。顯然,咱們不須要手動肯定哪些內容是首屏關鍵 CSS。Github 上有一個項目Critical CSS4,能夠將屬於首屏的關鍵樣式提取出來,你們能夠看一下該項目,結合本身的構建工具進行使用。固然爲了保證正確,你們最好再親自確認下提取出的內容是否有缺失。

不過內聯 CSS 有一個缺點,內聯以後的 CSS 不會進行緩存,每次都會從新下載。不過如上所說,若是咱們將內聯後的文件大小控制在了 14.6kb 之內,這彷佛並非什麼大問題。

如上,咱們已經介紹了爲何要內聯關鍵 CSS 以及如何內聯,那麼剩下的 CSS 咱們怎麼處理好呢?建議使用外部 CSS 引入剩餘 CSS,這樣可以啓用緩存,除此以外還能夠異步加載它們。

2. 異步加載 CSS

CSS 會阻塞渲染,在 CSS 文件請求、下載、解析完成以前,瀏覽器將不會渲染任何已處理的內容。有時,這種阻塞是必須的,由於咱們並不但願在所需的 CSS 加載以前,瀏覽器就開始渲染頁面。那麼將首屏關鍵 CSS 內聯後,剩餘的 CSS 內容的阻塞渲染就不是必需的了,可使用外部 CSS,而且異步加載。

那麼如何實現 CSS 的異步加載呢?有如下四種方式能夠實現瀏覽器異步加載 CSS。

第一種方式是使用 JavaScript 動態建立樣式表 link 元素,並插入到 DOM 中。

// 建立link標籤
const myCSS = document.createElement( "link" );
myCSS.rel = "stylesheet";
myCSS.href = "mystyles.css";
// 插入到header的最後位置
document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling );
複製代碼

第二種方式是將 link 元素的media屬性設置爲用戶瀏覽器不匹配的媒體類型(或媒體查詢),如media="print",甚至能夠是徹底不存在的類型media="noexist"。對瀏覽器來講,若是樣式表不適用於當前媒體類型,其優先級會被放低,會在不阻塞頁面渲染的狀況下再進行下載。

固然,這麼作  只是爲了實現 CSS 的異步加載, 別忘了在文件加載完成以後,將media的值設爲screenall,從而讓瀏覽器開始解析 CSS。

<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
複製代碼

與第二種方式類似,咱們還能夠經過rel屬性將link元素標記爲alternate可選樣式表,也能實現瀏覽器異步加載。一樣別忘了加載完成以後,將rel改回去。

<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
複製代碼

上述的三種方法都較爲古老。如今,rel="preload"5這一 Web 標準指出瞭如何異步加載資源,包括 CSS 類資源。

<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">
複製代碼

注意,as是必須的。忽略as屬性,或者錯誤的as屬性會使preload等同於XHR請求,瀏覽器不知道加載的是什麼內容,所以此類資源加載優先級會很是低。as的可選值能夠參考上述標準文檔。

看起來,rel="preload"的用法和上面兩種沒什麼區別,都是經過更改某些屬性,使得瀏覽器異步加載 CSS 文件但不解析,直到加載完成並將修改還原,而後開始解析。

可是它們之間其實有一個很重要的不一樣點,那就是使用 preload,比使用不匹配的media方法可以更早地開始加載 CSS。因此儘管這一標準的支持度還不完善,仍建議優先使用該方法。

該標準如今已是候選標準,相信瀏覽器會逐漸支持該標準。 在各瀏覽器的支持度以下圖所示。

preload瀏覽器支持度
preload瀏覽器支持度

從上圖能夠看出這一方法  在如今的瀏覽器中支持度不算樂觀,不過咱們能夠經過loadCSS6進行 polyfill,因此支持不支持,這都不是事兒。

3. 文件壓縮

性能優化時有一個最容易想到,也最常使用的方法,那就是文件壓縮,這一方案每每效果顯著。

文件的大小會直接影響瀏覽器的加載速度,這一點在網絡較差時表現地尤其明顯。相信你們都早已習慣對 CSS 進行壓縮,如今的構建工具,如 webpack、gulp/grunt、rollup 等也都支持 CSS 壓縮功能。壓縮後的文件可以明顯減少,能夠大大下降了瀏覽器的加載時間。

4. 去除無用 CSS

雖然文件壓縮可以下降文件大小。但 CSS 文件壓縮一般只會去除無用的空格,這樣就限制了 CSS 文件的壓縮比例。那是否還有其餘手段來精簡 CSS 呢?答案顯然是確定的,若是壓縮後的文件仍然超出了預期的大小,咱們能夠試着找到並刪除代碼中無用的 CSS

通常狀況下,會存在這兩種無用的 CSS 代碼:一種是不一樣元素或者其餘狀況下的重複代碼,一種是整個頁面內沒有生效的 CSS 代碼。對於前者,在編寫的代碼時候,咱們應該儘量地提取公共類,減小重複。對於後者,在不一樣開發者進行代碼維護的過程當中,總會產生再也不使用的 CSS 的代碼,固然一我的編寫時也有可能出現這一問題。而這些無用的 CSS 代碼不只會增長瀏覽器的下載量,還會增長瀏覽器的解析時間,這對性能來講是很大的消耗。因此咱們須要找到並去除這些無用代碼。

固然,若是手動刪除這些無用 CSS 是很低效的。咱們能夠藉助Uncss7庫來進行。Uncss 能夠用來移除樣式表中的無用 CSS,而且支持多文件和 JavaScript 注入的 CSS。


前面已經說完了實踐型的 4 個優化技巧,下面咱們介紹下建議型的 4 個技巧

1. 有選擇地使用選擇器

大多數朋友應該都知道CSS 選擇器的匹配是從右向左進行的,這一策略致使了不一樣種類的選擇器之間的性能也存在差別。相比於#markdown-content-h3,顯然使用#markdown .content h3時,瀏覽器生成渲染樹(render-tree)所要花費的時間更多。由於後者須要先找到 DOM 中的全部h3元素,再過濾掉祖先元素不是.content的,最後過濾掉.content的祖先不是#markdown的。試想,若是嵌套的層級更多,頁面中的元素更多,那麼匹配所要花費的時間代價天然更高。

不過現代瀏覽器在這一方面作了不少優化,不一樣選擇器的性能差異並不明顯,甚至能夠說差異甚微。此外不一樣選擇器在不一樣瀏覽器中的性能表現8也不徹底統一,在編寫 CSS 的時候沒法兼顧每種瀏覽器。鑑於這兩點緣由,咱們在使用選擇器時,只須要記住如下幾點,其餘的能夠全憑喜愛。

  1. 保持簡單,不要使用嵌套過多過於複雜的選擇器。
  2. 通配符和屬性選擇器效率最低,須要匹配的元素最多,儘可能避免使用。
  3. 不要使用類選擇器和 ID 選擇器修飾元素標籤,如h3#markdown-content,這樣畫蛇添足,還會下降效率。
  4. 不要爲了追求速度而放棄可讀性與可維護性。

若是你們對於上面這幾點還存在疑問,筆者建議你們選擇如下幾種 CSS 方法論之一(BEM9OOCSS10SUIT11SMACSS12ITCSS13Enduring CSS14等)做爲 CSS 編寫規範。使用統一的方法論可以幫助你們造成統一的風格,減小命名衝突,也能避免上述的問題,總之好處多多,若是你尚未使用,就趕快用起來吧。

Tips:爲何 CSS 選擇器是從右向左匹配的?

CSS 中更多的選擇器是不會匹配的,因此在考慮性能問題時,須要考慮的是如何在選擇器不匹配時提高效率。從右向左匹配就是爲了達成這一目的的,經過這一策略可以使得 CSS 選擇器在不匹配的時候效率更高。這樣想來,在匹配時多耗費一些性能也可以想的通了。

2. 減小使用昂貴的屬性

在瀏覽器繪製屏幕時,全部須要瀏覽器進行操做或計算的屬性相對而言都須要花費更大的代價。當頁面發生重繪時,它們會下降瀏覽器的渲染性能。因此在編寫 CSS 時,咱們應該儘可能減小使用昂貴屬性,如box-shadow/border-radius/filter/透明度/:nth-child等。

固然,並非讓你們不要使用這些屬性,由於這些應該都是咱們常用的屬性。之因此提這一點,是讓你們對此有一個瞭解。當有兩種方案能夠選擇的時候,能夠優先選擇沒有昂貴屬性或昂貴屬性更少的方案,若是每次都這樣的選擇,網站的性能會在不知不覺中獲得必定的提高。

3. 優化重排與重繪

在網站的使用過程當中,某些操做會致使樣式的改變,這時瀏覽器須要檢測這些改變並從新渲染,其中有些操做所耗費的性能更多。咱們都知道,當 FPS 爲 60 時,用戶使用網站時纔會感到流暢。這也就是說,咱們須要在 16.67ms 內完成每次渲染相關的全部操做,因此咱們要儘可能減小耗費更多的操做。

3.1 減小重排

重排會致使瀏覽器從新計算整個文檔,從新構建渲染樹,這一過程會下降瀏覽器的渲染速度。以下所示,有不少操做會觸發重排,咱們應該避免頻繁觸發這些操做。

  1. 改變 font-sizefont-family
  2. 改變元素的內外邊距
  3. 經過 JS 改變 CSS 類
  4. 經過 JS 獲取 DOM 元素的位置相關屬性(如 width/height/left 等)
  5. CSS 僞類激活
  6. 滾動滾動條或者改變窗口大小

此外,咱們還能夠經過CSS Trigger15查詢哪些屬性會觸發重排與重繪。

值得一提的是,某些 CSS 屬性具備更好的重排性能。如使用Flex時,比使用inline-blockfloat時重排更快,因此在佈局時能夠優先考慮Flex

3.2 避免沒必要要的重繪

當元素的外觀(如 color,background,visibility 等屬性)發生改變時,會觸發重繪。在網站的使用過程當中,重繪是沒法避免的。不過,瀏覽器對此作了優化,它會將屢次的重排、重繪操做合併爲一次執行。不過咱們仍須要避免沒必要要的重繪,如頁面滾動時觸發的 hover 事件,能夠在滾動的時候禁用 hover 事件,這樣頁面在滾動時會更加流暢。

此外,咱們編寫的 CSS 中動畫相關的代碼愈來愈多,咱們已經習慣於使用動畫來提高用戶體驗。咱們在編寫動畫時,也應當參考上述內容,減小重繪重排的觸發。除此以外咱們還能夠經過硬件加速16will-change17來提高動畫性能,本文不對此展開詳細介紹,感興趣的小夥伴能夠點擊連接進行查看。

最後須要注意的是,用戶的設備可能並無想象中的那麼好,至少不會有咱們的開發機器那麼好。咱們能夠藉助 Chrome 的開發者工具進行 CPU 降速,而後再進行相關的測試,降速方法以下圖所示。

如何開啓Chrome的CPU降速
如何開啓Chrome的CPU降速

若是須要在移動端訪問的,最好將速度限制更低,由於移動端的性能每每更差。

4. 不要使用@import

最後提一下,不要使用@import 引入 CSS,相信你們也不多使用。

不建議使用@import 主要有如下兩點緣由。

首先,使用@import 引入 CSS 會影響瀏覽器的並行下載。使用@import 引用的 CSS 文件只有在引用它的那個 css 文件被下載、解析以後,瀏覽器纔會知道還有另一個 css 須要下載,這時纔去下載,而後下載後開始解析、構建 render tree 等一系列操做。這就致使瀏覽器沒法並行下載所需的樣式文件。

其次,多個@import 會致使下載順序紊亂。在 IE 中,@import 會引起資源文件的下載順序被打亂,即排列在@import 後面的 js 文件先於@import 下載,而且打亂甚至破壞@import 自身的並行下載

因此不要使用這一方法,使用 link 標籤就好了。


總結

至此,咱們介紹完了 CSS 性能優化的 4 個實踐型技巧和 4 個建議型技巧,在瞭解這些技巧以後,CSS 的性能優化從如今就能夠開始了。不要猶豫了,儘快開始吧。

致謝

特別感謝@anjia(安佳)、@劉宇晨、@hxl(黃小璐)、@劉觀宇的辛苦審校,感謝大家對於文章結構和內容提出的寶貴建議。

文內連接

  1. https://mp.weixin.qq.com/s/C2Zx3KPNPkgj-aHnOY43Iw
  2. https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/
  3. https://tylercipriani.com/blog/2016/09/25/the-14kb-in-the-tcp-initial-window/
  4. https://github.com/filamentgroup/criticalCSS
  5. https://www.w3.org/TR/preload/
  6. https://github.com/filamentgroup/loadCSS/tree/v2.0.1#loadcss
  7. https://github.com/uncss/uncss
  8. https://benfrain.com/css-performance-revisited-selectors-bloat-expensive-styles/#h-H1_1
  9. https://en.bem.info/methodology/quick-start/
  10. http://oocss.org/
  11. https://github.com/suitcss/suit/blob/master/doc/naming-conventions.md
  12. https://smacss.com/
  13. https://www.xfive.co/blog/itcss-scalable-maintainable-css-architecture/
  14. http://ecss.io/
  15. https://csstriggers.com/
  16. https://www.sitepoint.com/introduction-to-hardware-acceleration-css-animations/
  17. https://drafts.csswg.org/css-will-change/

參考文章

  1. Efficiently Rendering CSS
  2. How to write CSS for a great performance web application
  3. CSS performance revisited: selectors, bloat and expensive styles
  4. Avoiding Unnecessary Paints
  5. Five CSS Performance Tools to Speed up Your Website
  6. How and Why You Should Inline Your Critical CSS
  7. Render blocking css
  8. Modern Asynchronous CSS Loading
  9. Preload