我在Medium瀏覽帖子的時候發現他們的圖片加載效果真的很贊誒。首先載入一個低像素的模糊圖片,然后逐漸轉變為高清大圖。這個過程體驗真的很好,所以我希望能夠明白他們是使用什么方法做到的。

Medium的技術
我使用WebPageTest測試這個頁面的載入過程。如果你希望能夠測試同樣效果,可以打開Medium的頁面,禁用cache減慢圖片申請加載的過程,所以加載出原圖的時間會稍久。這樣就可以清楚看到整個圖片的加載效果。
具體執行過程
使用div限定好圖片展示的區域,Medium使用div>標簽并加入padding-bottom樣式設定對應圖片的展示尺寸。通過這樣占位的方式可以防止在圖片加載后出現整體頁面回流的情況。這種方法通常被稱為intrinsic placeholders
加載小尺寸(像素低)的圖片,此時網頁會先請求一個像素質量較低的小號縮略圖(大小為原圖的20%).這個小圖片使用img />標簽,因此瀏覽器會在標簽加載完成后,立即請求圖片資源。
只要圖片加載完成,它就會被“畫”到canvas />中。圖片數據會被main-base.bundle.js文件中自定義的Blur()函數重新計算,可以看到它會產生模糊圖片的效果。盡管有些不同,不過該函數與StackBlur的模糊函數實現效果是相似的。在模糊圖片生成的同時,瀏覽器也會開始請求高清原圖資源。
最后原圖被加載到頁面上,canvas會被隱藏,只展示原圖。
最后的最后,感謝CSS的動畫功能,上述所有轉變過程會很流暢。
Markup
整個展示圖片的結構
XML/HTML Code復制內容到剪貼板
- figure>
- div>
- div/>
- img/>
- canvas/>
- img/>
- noscript/>
- /div>
- /figure>
- figure name="7012" id="7012" class="graf--figure graf--layoutFillWidth graf-after--h4">
- div class="aspectRatioPlaceholder is-locked">
- div class="aspect-ratio-fill" style="padding-bottom: 66.7%;">/div>
- div class="progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded" data-image-id="1*sg-uLNm73whmdOgKlrQdZA.jpeg" data-width="2000" data-height="1333" data-scroll="native">
- img src="https://cdn-images-1.medium.com/freeze/max/27/1*sg-uLNm73whmdOgKlrQdZA.jpeg?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail">
- canvas class="progressiveMedia-canvas js-progressiveMedia-canvas" width="75" height="47">/canvas>
- img class="progressiveMedia-image js-progressiveMedia-image __web-inspector-hide-shortcut__" data-src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg" src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg">
- noscript class="js-progressiveMedia-inner">img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg">/noscript>
- /div>
- /div>
- /figure>
PS:實際圖片大小要根據設備尺寸來設定。
嘗試重新實現同樣效果
我在CodePen重新通過使用CSS替代canvas實現同樣的加載效果。下面的圖片展示了整個加載過程中,圖片的轉變效果。

這么做是否值?
很明顯,現在有許多種方法來實現同樣的效果。要知道在幾年前如此高性能的方式實現動畫和模糊效果還是不可能的。但事實上,大多數時候延遲瓶頸,并不是設備本身的原因,因此這些技巧值得我們探索。 控制加載圖片過程有以下優點:
懶加載:使用JS來請求資源讓我們可以靈活控制圖片資源選擇。小圖可以請求同一縮略圖,大圖則可以根據瀏覽器視窗大小來選擇加載尺寸不同的圖片。
更好的占位符: 相比于純色占位符,使用縮略圖添加模糊效果后會有更好的視覺效果,同時圖片大小也只有2k左右不會犧牲負載。
裁剪圖片大小:Medium根據訪問設備的不同,返回不同尺寸的圖片,這樣可以很好的優化頁面的加載速度,同時避免移動設備浪費過多流量。
其他版本
在實現Medium原方法之前,我覺得我可以在我的網站使用其他方法來實現。
內聯圖片數據
我們可以在img中添加縮略圖的URLs來直接請求資源。這樣做雖然會增加HTML的內容,但是可以加快占位符的生成速度。瀏覽器加載好HTML標簽就立即下載圖片文件資源。加了模糊效果后圖片的質量就無所謂了,我測試使用0.5k大小的圖片與2k大小的圖片得到相似的顯示效果。
模糊效果
默認情況下,當瀏覽器將一個小圖像放大,它應用光滑效果處理圖像的模糊效果。圖像的效果也可以關閉,像QR碼。
[…]the browser would render it in a way that didn’t make it look blocky[…] from Google Developers.
它可以在Chrome、Safari和Firefox中有效,盡管光滑效果在Chrome中更有效,你可以在這里看效果。
下面我們看看如何做到光滑效果。圖片只有27px寬,并且像素非常低,將它放大會產生很可怕的效果。事實卻并沒有。如果上述效果能滿足你的要求,那你就不需要更復雜的效果替換了。
上述圖片模糊效果也可以使用CSS Filter Effects實現,它還支持IE瀏覽器哦(IE一生黑)。我相信Medium在使用canvas方法之前一定也嘗試過使用這個功能更強的方法。但是可能是出于一定原因他們放棄了這一方法。這一方法的優點是你可以設定模糊度,并且可以通過CSS達成其他目的。
也可以使用SVG的filter來達成同樣目的,如The “Blur Up” Technique for Loading Background Images 和 Textured Gradients in Pure CSS兩篇文章提到的。
其他辦法提升占位符:Google 圖片搜索

他們選擇圖片的一種主顏色,并用其作為占位塊的背景色。這樣做會給用戶一種圖片加載速度更快的體驗。
更先進的方法:Facebook的200 byte技術
年初Facebook發表過一篇"The technology behind preview photos"的文章,這篇文章主要說明如何預覽一個沒有JPEG頭的42 * 42px的圖片。 使用場景有些不同,這“圖片”被用于Facebook的手機端,它知道如何組成一個有效的JPEG圖片。此處我們在Web端使用的話需要編寫JavaScript代碼,這樣做同樣會增加存儲資源。當然我們可以通過在服務器端組成這個圖片解決這一問題,但是這樣仍需要一些JavaScript代碼發送申請圖片資源的請求。
無論如何,這個方法對于Web端來說有點大材小用,但我還是希望能夠將其作為一個參考。Using WebP for generating this preview images同樣可以節省內存,并且不需要使用如此創造性的解決方法。
LQIP: Low Quality Image Placeholders
與其等待最終的圖像呈現,我們可以先提供一個高度壓縮的圖片,然后切換到大圖。這就是LQIP方法的思路。這一方法與Medium相似,不過是使用相同尺寸,但壓縮更高的圖片。
總結
隨著頁面加載的圖片越來越多,需要勤于思考頁面的加載過程。因為這會影響加載效率和用戶體驗。 如果你生成幾個縮略圖大小的圖片,你可以實驗使用一個非常小的圖片作為背景,等待最終圖片被加載出來。