原文做者爲Harry,分爲兩部分:Part1 & Part2。php
減小請求數量,是這幾年的一個優秀性能建議。雖然如此,也不是說它就沒有缺陷。爲了使頁面加載更快,咱們實際上能夠經過高效的傳輸靜態資源來實現,而不僅是減小几個請求。css
其中一個從減小請求數量誕生並被推崇的實踐是使用Base64編碼:將外部資源(e.g. 圖片)直接嵌入到使用它的文本(e.g.樣式表)中。減小HTTP請求數量的關鍵是,全部資源(樣式表或圖片)可以在同一時間到達。聽起來像作夢,是吧?html
然而並非。git
不幸的是,使用Base64編碼是一個反模式[注1]。我但願在這篇文章中去分享關於關鍵路徑優化,Gzip,固然還有Base64的一些思考。github
我寫這篇文章是由於我剛剛爲客戶作了一個審計,遇到了下面要討論到的問題。這是一個來自實際客戶端的實際樣式表:信息是匿名的,但這是一個徹底真實的項目。web
我在頁面上運行了一個快速的網絡配置文件,發現了一個樣式表(某方面來講,這是件好事,由於咱們是絕對不肯意看到有12個樣式表請求的),可是這個樣式表在解壓縮以後竟然有925K。實際請求到的字節少得多,但仍是有232K。瀏覽器
當咱們看到這麼大致積的樣式表時,開始感到恐慌了。我至關肯定,甚至都不用去看,裏面確定有Base64。固然,並非說它是惟一的緣由(插件,缺少結構,繼承等等,均可能有影響),但這麼大致積的樣式表一般都是由於Base64。而且:緩存
無論是否是由於Base64,925K的樣式表都很恐怖性能優化
壓縮也只能減小到759K網絡
Gzip壓縮到232K,去除了693K相同的代碼
232K的請求仍是很恐怖
請注意,光是解析這麼大的樣式表就須要88ms。而把它交給網絡只是咱們煩惱的開始而已:
我優化了文件[注2],把它保存到個人機器,用CSSO運行,而後用Gzip的常規設置執行縮小後的內容。看看我獲得的數字:
harryroberts in ~/Sites/<client>/review/code on (master) » csso base64.css base64.min.css harryroberts in ~/Sites/<client>/review/code on (master) » gzip -k base64.min.css harryroberts in ~/Sites/<client>/review/code on (master) » ls -lh total 3840 -rw-r--r-- 1 harryroberts staff 925K 10 Feb 11:23 base64.css -rw-r--r-- 1 harryroberts staff 759K 10 Feb 11:24 base64.min.css -rw-r--r-- 1 harryroberts staff 232K 10 Feb 11:24 base64.min.css.gz
接下來要作的就是找出有多少字節是Base64資源。爲了作這件事,我簡單粗暴的刪除了全部包含data:字符串(:g/data:/d[注3],Vim用戶閱讀)的行和聲明。這裏面大部分是圖片/雪碧圖,小部分是字體。而後我將這個刪除後的文件保存爲no-base64.css,再執行相同的壓縮和Gzip:
harryroberts in ~/Sites/<client>/review/code on (master) » ls -lh total 2648 -rw-r--r-- 1 harryroberts staff 708K 10 Feb 15:54 no-base64.css -rw-r--r-- 1 harryroberts staff 543K 10 Feb 15:54 no-base64.min.css -rw-r--r-- 1 harryroberts staff 68K 10 Feb 15:54 no-base64.min.css.gz
在尚未壓縮以前,咱們已經減小了217K的Base64內容。但仍是很大(708K),不過咱們已經成功移除了23.45%的Base64代碼。
在咱們使用Gzip以後,至關驚喜。咱們把708K降到了68K。整整減小了90.39%。
Gzip真的是太讓人難以置信了! 它多是世界上用於保護用戶免受開發者禍害的最好工具了。咱們只是經過壓縮CSS就成功的節省了90%。從708K到68K。
然而,這是Gzip在沒有Base64樣式表上的成果。若是咱們使用原來的CSS(有Base64),咱們只能減小74.91%。
Base64? | Gross Size | Compressed Size | Saving |
---|---|---|---|
Yes | 925K | 232K | 74.91% |
No | 708K | 68K | 90.39% |
兩個選項之間的差別是驚人的164K(70.68%)。而咱們只是經過移開那些更適合其餘地方的內容就可以減小164K的CSS。
因此,對Base64的壓縮是很低效的。下次若是有人說’用Gzip...’,能夠給他們看看這些結果(若是他們提倡使用Base64的話)。
咱們如今已經很清楚在某種程度上Gzip是沒辦法幫咱們處理Base64增長的文件大小,但這只是其中一小部分的問題。爲何咱們這麼懼怕增長文件的大小?單個圖片的大小就有可能超過232K,爲何咱們不從圖片開始解決呢?
好問題,我很高興你提到圖片...
爲了解釋Base64有多糟糕,咱們須要先知道圖片有多好用。一個很有爭議的觀點是:圖片的性能沒有你想象中的那麼差。
固然,圖片是個問題。實際上它們是頁面膨脹的首要貢獻者。截止2016年12月2日,圖片佔了平均網頁資源的1623K(64.46%)。對比起來,咱們232K的樣式表就不足爲道了吧。可是,瀏覽器在處理圖片和樣式表時是有着本質上的差異的:
無論圖片是否加載完瀏覽器都會開始渲染。即便圖片一直都加載不成功,瀏覽器也會渲染。圖片不是關鍵資源,即便它們佔用了過多的字節,它們也不是瓶頸。
而CSS是關鍵資源。瀏覽器在構建渲染樹以前沒法開始渲染頁面,在構建CSSOM以前沒法構造渲染樹,在全部樣式表加載完、解壓縮和解析以前沒法構造CSSOM。CSS纔是瓶頸。
如今但願你可以明白爲何咱們如此在乎CSS的大小:它們只會延遲頁面渲染,而且讓用戶盯着空白的屏幕看。但願你同時可以意識到Base64將圖片轉換爲CSS文件內容是一件很荒謬的事情:爲了追求性能,你剛剛將數百K的非阻塞資源變成了阻塞資源。全部這些圖片均可以經過網絡準備就緒,但它們如今卻被迫與關鍵資源一塊兒出現。這並不意味着圖片會更快;它意味着關鍵資源會更慢。還有比這更糟糕的嗎?!
固然有。
瀏覽器是很聰明的。它爲咱們作了不少的性能優化,很顯然它們更專業。讓咱們考慮一下響應式:
.masthead { background-image: url(masthead-small.jpg); } @media screen and (min-width: 45em) { .masthead { background-image: url(masthead-medium.jpg); } } @media screen and (min-width: 80em) { .masthead { background-image: url(masthead-large.jpg); } }
咱們給瀏覽器提供了三種可選的圖片,但它只會下載其中一個。它決定須要哪一個,而後下載,另外兩個則不會使用。
可是若是咱們用Base64,三個圖片都會下載,實際開銷是本來的三倍左右。下面是這個項目一段真實的CSS代碼(爲了顯示,我移除了data,在Gzip以前,這段代碼總共是26K;以後是18K):
@media only screen and (-moz-min-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio:2/1), only screen and (-webkit-min-device-pixel-ratio:2), only screen and (min-device-pixel-ratio:2), only screen and (min-resolution:2dppx), only screen and (min-resolution:192dpi) { .social-icons { background-image:url("data:image/png;base64,..."); background-size: 165px 276px; } .sprite.weather { background-image: url("data:image/png;base64,..."); background-size: 260px 28px; } .menu-icons { background-image: url("data:image/png;base64,..."); background-size: 200px 276px; } }
全部用戶,不管是否使用retina設備(即使用戶的瀏覽器不支持media queries),都將被迫下載額外的18K CSS,而後他們的瀏覽器甚至會把它們放在一塊兒。
不管是否會被使用,Base64資源必定會被下載。真浪費,可是當你以爲這是浪費的時候,實際上阻礙渲染纔是更糟糕的事情。
到目前爲止,我只提到圖片,可是除了瀏覽器處理無樣式/不可見Flash(FOUT或FOIT)的一些細微差異外,字體和圖片幾乎同樣。在這個項目未壓縮的CSS中,字體總共有166K(Gzip以後124K,真是可怕的壓縮效果)。
不偏離文章主題太遠,咱們知道字體不是關鍵資源,這是好事:你的頁面渲染不須要它們。可是,不一樣瀏覽器會以不一樣方式處理Web字體:
Chrome和Firefox在3s內幾乎不顯示文字。若是字體在3s內加載成功,文字會從隱藏顯示爲你的自定義字體。若是字體在3s後仍是獲取不到,文字就會從隱藏顯示爲你定義的默認字體。這就是FOIT。
IE會當即顯示默認字體而後在你的自定義字體加載成功時當即切換。這是FOUT。我我的認爲這是最優雅的解決方案。
Safari則會一直等待你的自定義字體加載成功才顯示文字。若是字體一直獲取失敗,文字就永遠也不會出現。這是FOIT。這真的讓人難以接受。你的用戶幾乎沒法看到你網頁上的任何文字。
爲了解決這些問題,人們使用Base64將字體內聯到樣式表中:若是CSS和字體同時到達,就不會有什麼FOIT或FOUT,由於CSSOM和字體解析幾乎會同時發生。
跟圖片同樣,把你的字體轉移到關鍵資源並不會提升它們的效率,只會延遲你的CSS。實際上有一些很是好的字體加載解決方案,不過Base64不在其中。
Base64同時也對咱們複雜的緩存機制有影響:經過耦合字體,圖片和樣式,它們都受一樣的規則控制。這意味着即便咱們只是隨便改變CSS的一個hex值(可能最多就六個字節的數據更改),就須要從新下載幾百K的樣式,圖片和字體。
字體在這裏是真正的罪魁禍首:它們是不太可能會改變的穩定資源。事實上,我剛剛檢查了另一個客戶和我正在開展的一個長時間運行的項目:他們的CSS昨天剛修改;他們的字體倒是在8個月以前修改的。想象一下,每次樣式表有什麼修改,用戶都要被迫從新下載那些不變的字體。
Base64編碼意味着咱們沒辦法根據本身的變化來單獨緩存內容,也意味着不管是否有改變都須要緩存不變的信息。這是一個不論怎樣都輸的局面。
咱們須要關注基本分離:個人字體緩存不該該依賴於個人圖片緩存,個人圖片緩存也不該該依賴於個人樣式緩存。
好了,讓咱們快速的歸納下:
Base64增長了文件的大小但咱們卻沒法有效壓縮(e.g.Gzip)。而這種行爲會延遲加載,阻塞渲染。
Base64把非關鍵資源(e.g.圖片,字體)放到關鍵資源(e.g.樣式表)中。這意味着在這種特殊狀況下,在咱們開始渲染頁面以前,相比起68K的CSS,咱們須要下載超過3.4倍的內容。咱們白白的讓用戶等待那些他們本來並不須要等待的內容!
Base64強制全部內容都須要下載,即便它們根本就不會被用到。這是一種浪費,並且還發生在咱們的關鍵資源中。
Base64限制了咱們獨立緩存的能力;咱們的圖片和字體被樣式綁定,反之亦然。
總而言之,請避免使用Base64。
這篇文章寫的都是我知道的。我並無執行測試來證實:這只是瀏覽器的工做原理。可是,我仍是決定往前一步,執行一些測試來看看咱們所尋求的是怎樣的事實和數據。具體請看第二部份內容。
爲了證實使用Base64將靜態資源(主要是圖片)內嵌到樣式表中的缺陷,我決定實際收集一些證據。我設置了一個簡單的測試,對比’傳統’加載資源和Base64兩種方案的一些重要階段和運行時間。
讓咱們從兩個簡單的被背景圖片覆蓋的HTML文件開始,第一個是普通加載,第二個是用Base64:
我把它調整爲1440x900px,經過JPEGMini和ImageOptim處理後保存爲JPEG,而後才轉化爲Base64編碼:
harryroberts in ~/Sites/csswizardry.net/demos/base64 on (gh-pages) » base64 -i masthead.jpg -o masthead.txt
這是爲了使圖片適當優化,獲得與實際狀況更符合的Base64版本。
接着我新建了兩個樣式表:
* { margin: 0; padding: 0; box-sizing: border-box; } .masthead { height: 100vh; background-image: url("[masthead.jpg|<data URI>]"); background-size: cover; }
我把準備好的演示文件放在了一個實際網址上,這樣咱們就能夠得到真實的延遲和帶寬體驗。
我在Chrome中打開了一個特定的性能測試配置文件,關閉其餘打開的頁面,準備好開始。
開啓Chrome的Timeline開始測量。整個過程大概是這樣:
禁用緩存
清除剩餘Timeline信息
刷新頁面並記錄網絡和Timeline活動
丟棄任何關於DNS或TCP的鏈接結果(我不但願時間受到不相關網絡活動的影響)
記錄DOMContentLoaded,Load,First Paint,Parse Stylesheet和Image Decode
重複以上步驟直到獲得5組乾淨的數據
隔離每一個記錄的中位數(中位數是矯正的平均值)
針對Base64再次執行以上全部操做
在移動設備上再作一遍(最終獲得四組數據:PC和移動端的Base64和非Base64[注4])
第4點是最重要的:任何鏈接活動都會使結果發生傾斜並致使不一致,咱們只在絕對零鏈接開銷的狀況下才保留結果。
我經過調節CPU爲3倍,網絡爲常規的2G,來模擬中檔移動設備,併爲移動設備完成了大量的測試。
在Google Sheets上你能看到我收集的全部數據(全部數字的單位都是毫秒)。令我震驚的是數據的質量和一致性:不多有異常值。
如今先忽略預加載圖片的數據(請看接下來的:第三種方法)。PC和移動端分爲不一樣的表格(切換數據底部的標籤)。
數據是很直觀的,它們證明了個人不少猜測。你本身能夠隨意查看裏面的細節,但我已經提取了最相關和有意義的信息:
在PC和移動端之間,DOMContentLoaded事件在很大程度上保持不變。這裏並無什麼’更好的選擇’。
Load Event在移動端的Base64和非Base64類似,可是PC端的Base64是非Base64的2.02倍(正常:236ms,Base64:476ms)。Base64更慢。
parsing stylesheets的Base64明顯更慢。在PC端慢了超過10倍。在移動端慢了超過32倍。Base64更慢。
在PC端,Base64解壓縮快過普通圖片的1.23倍。Base64更快。
可是在移動端,普通圖片解壓縮快過Base64圖片2.05倍。Base64更慢。
First Paint是測量感知性能的一個很大的指標:它告訴咱們用戶什麼時候開始看到內容。在PC端,普通圖片的First Paint發生在280ms,可是Base64在629ms:Base64慢了2.25倍。
在移動端,普通圖片的First Paint發生在774ms,Base64在7950ms。Base64慢了10.27倍。換句話說,普通圖片在1s內開始繪製,Base64幾乎要到8s纔開始。使人咋舌,Base64明顯比較慢。
經過以上這些信息能夠很明顯看出誰是完美的贏家:幾乎全部方面,全部平臺,若是咱們遠離Base64,會更快。咱們尤爲須要關注具備更高延遲和受限的性能和帶寬的低功耗設備,這裏是重災區:32倍慢的stylesheet和10.27倍慢的First Paint。
普通圖片的加載有一個問題是對瀑布流的影響:咱們須要下載HTML,HTML請求CSS,CSS請求圖片,這是同步的過程。Base64的理論優點是能夠同時加載CSS和圖片(實際上不是,雖然它們一塊兒出現,可是它們也一塊兒遲到了),這可讓咱們使用更加併發的方式來加載資源。
幸運的是,有一種方法能夠作到不用把全部圖片內嵌入樣式表就能夠實現並行。經過提早加載,而不是將圖片做爲一個遲來的資源,像這樣:
<link rel="preload" href="masthead.jpg" as="image" />
我作了另一個演示頁面:
經過將這個標籤放到HTML的頭部,咱們能夠告訴HTML直接下載圖片,而不用等CSS去請求它。這意味着,取代像這樣的請求鏈:
| |-- HTML --| | |- CSS -| | |---------- IMAGE ----------| |
咱們能夠這樣:
| |-- HTML --| | |---------- IMAGE ----------| |- CSS -| | |
注意:
咱們獲取完整內容有多快?
圖片在CSS以前加載會怎麼樣?
預加載可讓咱們手動提早加載靜態資源,再在以後的頁面呈現。
我決定作一個普通圖片的頁面,取代CSS請求,我打算使用預加載:
<link rel="preload" href="masthead.jpg" as="image" /> <title>Preloaded Image</title> <link rel="stylesheet" href="image.css" />
我沒有發現這個測試用例有多大的改進,預加載在這裏並無頗有用:個人請求鏈過短,咱們沒有得到從新排序的真正好處。然而,若是咱們的頁面有許多靜態資源,預加載能夠給咱們帶來很大的益處。我在個人主頁上使用它來預加載標頭:以這種方式使用它確實產生了感知上的一些重大變化。
然而我注意到一個很是有趣的事情,關於解碼時間。在移動端,圖片在25ms內解碼,而PC端須要36.57ms。
預加載圖片在移動端解碼是PC端的1.46倍快。
預加載圖片在移動端解碼是沒有預加載的3.53倍快。
我不肯定爲何會這樣,我簡單粗暴的猜想:也許圖片在實際須要以前不會被解碼,因此若是在實際須要解碼以前,在設備上已經有一堆字節了,那麼這個過程能夠更快地工做嗎?任何在讀這篇文章的人若是有誰知道這個答案的,請告訴我!
我雖然儘可能保持個人測試公平和不受影響,但若是給我更多時間我能夠作得更好(不過這是週末嘛...):
在真實設備上測試。我經過DevTools調節個人CPU和鏈接,可是在真實設備上運行這些測試無疑會更好。
在移動端用一個更合適的圖片。我在儘量多的變量中保持相同的測試,在PC和移動端使用同樣的圖片。實際上,我只是模擬移動設備的網絡能力,並無使用較小的屏幕或資源。但願在現實世界中,咱們能夠爲更小的設備提供一張更小的圖片(在尺寸和文件大小上)。而我只是在徹底相同的視圖中加載徹底相同的文件,只修改了鏈接和CPU。
測試一個更真實的項目。這些都是實驗,正如我在預加載中指出的,這並無頗有效。但願在非測試環境可以看到不同的結果。
這就是我關於Base64性能影響的兩篇文章。它雖然看起來像是在描述一些咱們已經知道的事實,可是可以看到一些數字證實仍是很好的,特別是低端設備。Base64感受上仍是像一個巨大的反模式。
[注1] 在一些很是特殊的狀況下它也許是明智的選擇,但除非你絕對肯定,不然那可能就是反模式。總之要很是謹慎,並始終認爲Base64不是正確的選擇。
[注2] 在Chrome的Sources中打開樣式表,按文件左下角的{}。
[注3] 在全部行中運行全局命令(:g); 找到包含數據的行:(/ data :)並刪除它們(/ d)。
[注4] 這裏須要解釋一下:我基本上是在個人筆記本電腦和一個模擬的移動設備上進行測試的,我並非在說屏幕尺寸。