本文做者:高峯,360奇舞團前端工程師,W3C性能工做組成員,同時參與WOT工做組的學習。css
咱們都知道對於網站來講,性能相當重要,CSS做爲頁面渲染和內容展示的重要環節,影響着用戶對整個網站的第一體驗。所以,與其相關的性能優化是不容忽視的。html
對於性能優化咱們經常在項目完成時纔去考慮,常常被推遲到項目的末期,甚至到暴露出嚴重的性能問題時才進行性能優化,相信大多數人對此深有體會。前端
筆者認爲,爲了更多地避免這一狀況,首先要重視起性能優化相關的工做,將其貫穿到整個產品設計與開發中。其次,就是了解性能相關的內容,在項目開發過程當中,天然而然地進行性能優化。最後,也是最最重要的,那就是從如今開始實施優化。html5
推薦你們閱讀下奇舞週刊以前推的《嗨,送你一張Web性能優化地圖》1這篇文章,可以幫助你們對性能優化須要作的事以及須要考慮的問題造成一個總體的概念。webpack
本文將會詳細介紹CSS性能優化相關的技巧,筆者將它們分爲實踐型和建議型兩類,共8個小技巧。實踐型技巧可以快速地應用在項目中,可以很好地提高性能,也是筆者常用的,建議你們儘快在項目中實踐。建議型技巧中,有的可能對性能影響並不顯著,有的平時你們也並不會那麼用,因此筆者不會着重講述,讀者們能夠根據自身狀況瞭解一下便可。git
在正式開始以前,須要你們對於瀏覽器的工做原理2有些必定的瞭解,須要的小夥伴能夠先簡單瞭解下。github
下面咱們開始介紹實踐型的4個優化技巧,先從首屏關鍵CSS開始。web
性能優化中有一個重要的指標——首次有效繪製(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,這樣可以啓用緩存,除此以外還能夠異步加載它們。
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
的值設爲screen
或all
,從而讓瀏覽器開始解析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。因此儘管這一標準的支持度還不完善,仍建議優先使用該方法。
該標準如今已是候選標準,相信瀏覽器會逐漸支持該標準。在各瀏覽器的支持度以下圖所示。
從上圖能夠看出這一方法在如今的瀏覽器中支持度不算樂觀,不過咱們能夠經過loadCSS6進行polyfill,因此支持不支持,這都不是事兒。
性能優化時有一個最容易想到,也最常使用的方法,那就是文件壓縮,這一方案每每效果顯著。
文件的大小會直接影響瀏覽器的加載速度,這一點在網絡較差時表現地尤其明顯。相信你們都早已習慣對CSS進行壓縮,如今的構建工具,如webpack、gulp/grunt、rollup等也都支持CSS壓縮功能。壓縮後的文件可以明顯減少,能夠大大下降了瀏覽器的加載時間。
雖然文件壓縮可以下降文件大小。但CSS文件壓縮一般只會去除無用的空格,這樣就限制了CSS文件的壓縮比例。那是否還有其餘手段來精簡CSS呢?答案顯然是確定的,若是壓縮後的文件仍然超出了預期的大小,咱們能夠試着找到並刪除代碼中無用的CSS。
通常狀況下,會存在這兩種無用的CSS代碼:一種是不一樣元素或者其餘狀況下的重複代碼,一種是整個頁面內沒有生效的CSS代碼。對於前者,在編寫的代碼時候,咱們應該儘量地提取公共類,減小重複。對於後者,在不一樣開發者進行代碼維護的過程當中,總會產生再也不使用的CSS的代碼,固然一我的編寫時也有可能出現這一問題。而這些無用的CSS代碼不只會增長瀏覽器的下載量,還會增長瀏覽器的解析時間,這對性能來講是很大的消耗。因此咱們須要找到並去除這些無用代碼。
固然,若是手動刪除這些無用CSS是很低效的。咱們能夠藉助Uncss7庫來進行。Uncss能夠用來移除樣式表中的無用CSS,而且支持多文件和JavaScript注入的CSS。
前面已經說完了實踐型的4個優化技巧,下面咱們介紹下建議型的4個技巧。
大多數朋友應該都知道CSS選擇器的匹配是從右向左進行的,這一策略致使了不一樣種類的選擇器之間的性能也存在差別。相比於#markdown-content-h3
,顯然使用#markdown .content h3
時,瀏覽器生成渲染樹(render-tree)所要花費的時間更多。由於後者須要先找到DOM中的全部h3
元素,再過濾掉祖先元素不是.content
的,最後過濾掉.content
的祖先不是#markdown
的。試想,若是嵌套的層級更多,頁面中的元素更多,那麼匹配所要花費的時間代價天然更高。
不過現代瀏覽器在這一方面作了不少優化,不一樣選擇器的性能差異並不明顯,甚至能夠說差異甚微。此外不一樣選擇器在不一樣瀏覽器中的性能表現8也不徹底統一,在編寫CSS的時候沒法兼顧每種瀏覽器。鑑於這兩點緣由,咱們在使用選擇器時,只須要記住如下幾點,其餘的能夠全憑喜愛。
h3#markdown-content
,這樣畫蛇添足,還會下降效率。若是你們對於上面這幾點還存在疑問,筆者建議你們選擇如下幾種CSS方法論之一(BEM9,OOCSS10,SUIT11,SMACSS12,ITCSS13,Enduring CSS14等)做爲CSS編寫規範。使用統一的方法論可以幫助你們造成統一的風格,減小命名衝突,也能避免上述的問題,總之好處多多,若是你尚未使用,就趕快用起來吧。
Tips:爲何CSS選擇器是從右向左匹配的?
CSS中更多的選擇器是不會匹配的,因此在考慮性能問題時,須要考慮的是如何在選擇器不匹配時提高效率。從右向左匹配就是爲了達成這一目的的,經過這一策略可以使得CSS選擇器在不匹配的時候效率更高。這樣想來,在匹配時多耗費一些性能也可以想的通了。
在瀏覽器繪製屏幕時,全部須要瀏覽器進行操做或計算的屬性相對而言都須要花費更大的代價。當頁面發生重繪時,它們會下降瀏覽器的渲染性能。因此在編寫CSS時,咱們應該儘可能減小使用昂貴屬性,如box-shadow
/border-radius
/filter
/透明度/:nth-child
等。
固然,並非讓你們不要使用這些屬性,由於這些應該都是咱們常用的屬性。之因此提這一點,是讓你們對此有一個瞭解。當有兩種方案能夠選擇的時候,能夠優先選擇沒有昂貴屬性或昂貴屬性更少的方案,若是每次都這樣的選擇,網站的性能會在不知不覺中獲得必定的提高。
在網站的使用過程當中,某些操做會致使樣式的改變,這時瀏覽器須要檢測這些改變並從新渲染,其中有些操做所耗費的性能更多。咱們都知道,當FPS爲60時,用戶使用網站時纔會感到流暢。這也就是說,咱們須要在16.67ms內完成每次渲染相關的全部操做,因此咱們要儘可能減小耗費更多的操做。
重排會致使瀏覽器從新計算整個文檔,從新構建渲染樹,這一過程會下降瀏覽器的渲染速度。以下所示,有不少操做會觸發重排,咱們應該避免頻繁觸發這些操做。
font-size
和font-family
此外,咱們還能夠經過CSS Trigger15查詢哪些屬性會觸發重排與重繪。
值得一提的是,某些CSS屬性具備更好的重排性能。如使用Flex
時,比使用inline-block
和float
時重排更快,因此在佈局時能夠優先考慮Flex
。
當元素的外觀(如color,background,visibility等屬性)發生改變時,會觸發重繪。在網站的使用過程當中,重繪是沒法避免的。不過,瀏覽器對此作了優化,它會將屢次的重排、重繪操做合併爲一次執行。不過咱們仍須要避免沒必要要的重繪,如頁面滾動時觸發的hover事件,能夠在滾動的時候禁用hover事件,這樣頁面在滾動時會更加流暢。
此外,咱們編寫的CSS中動畫相關的代碼愈來愈多,咱們已經習慣於使用動畫來提高用戶體驗。咱們在編寫動畫時,也應當參考上述內容,減小重繪重排的觸發。除此以外咱們還能夠經過硬件加速16和will-change17來提高動畫性能,本文不對此展開詳細介紹,感興趣的小夥伴能夠點擊連接進行查看。
最後須要注意的是,用戶的設備可能並無想象中的那麼好,至少不會有咱們的開發機器那麼好。咱們能夠藉助Chrome的開發者工具進行CPU降速,而後再進行相關的測試,降速方法以下圖所示。
若是須要在移動端訪問的,最好將速度限制更低,由於移動端的性能每每更差。
最後提一下,不要使用@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(黃小璐)、@劉觀宇的辛苦審校,感謝大家對於文章結構和內容提出的寶貴建議。
- Efficiently Rendering CSS
- How to write CSS for a great performance web application
- CSS performance revisited: selectors, bloat and expensive styles
- Avoiding Unnecessary Paints
- Five CSS Performance Tools to Speed up Your Website
- How and Why You Should Inline Your Critical CSS
- Render blocking css
- Modern Asynchronous CSS Loading
- Preload