用純 CSS 與 LQIP 實現閃電般快速的圖片預覽
原文由 Matthias Endler 于 發布,訂閱此部落格
我的網站速度還算快。
我希望無論你用什麼裝置、在哪裡瀏覽,每次載入頁面都能感覺很流暢。這應該不令人意外,畢竟我只用了單純的 HTML 和 CSS,能不用 JavaScript 就盡量不用。
還有最後一件事讓我很困擾:圖片載入後的版面重排。
問題在於,文字準備好要顯示時,還不知道圖片的尺寸。結果就是,一旦上方的圖片載入完成,文字就會被往下推。
而且,圖片載入期間不會有任何預覽,只會留下一片空白。在連線較慢的情況下,看起來就像這樣:

我本來可以寫死圖片的寬高來解決,但那樣既麻煩又容易出錯,而且還是沒有預覽效果。於是我開始好奇,其他人都是怎麼做的。🤔
微小的圖片縮圖
我隱約記得,Facebook 在其行動版 App 中使用了極小的預覽縮圖。他們從 JPEG 檔頭中提取量化表來渲染預覽。這些資訊會儲存在客戶端,所以不需要每次都重新下載。可惜,這種做法需要完全掌控圖片的編碼器,對 App 來說可行,但對網站來說就很難實現。
搜尋還在繼續。
直到我的同事 Tobias Baldauf 向我介紹了 LQIP(低品質圖片占位符)。
概念是這樣的:
- 載入頁面時,一併載入內嵌的低品質圖片縮圖。
- 等到頁面完全載入(例如觸發
onload事件時),再延遲載入完整品質的圖片。
可惜,這項技術需要用到 JavaScript。不過我很喜歡這個想法,於是開始嘗試不同的圖片尺寸和格式。我的目標是用任何標準的圖片格式做出最小的縮圖。
效能測試
以下是用不同檔案格式編碼、寬度僅 15 像素的縮圖:

我用了不同的工具來製作縮圖。JPEG 和 PNG 的編碼是用 svgexport。
svgexport img.svg img.png "svg{background:white;}" 15: 1%WebP 則是用 cwebp:
cwebp img.png -o img.webpGIF 是用線上工具轉檔,再用 gifsicle 進行最佳化:
gifsicle -O3 < img.gif > img_mini.gif比較
WebP 檔案最小,但並非所有瀏覽器都支援。
GIF 排第二,但在縮放圖片並套用模糊濾鏡後,效果讓我不太滿意。
最後我選擇了 PNG,它在檔案大小和品質之間取得了很好的平衡。我還用支援 zopfli 壓縮的 oxipng 進一步最佳化圖片,最終縮圖大小大約只有 300 到 400 bytes。
我把縮圖的產生流程整合進了部落格的建置工具鏈中。實際產生圖片的程式碼相當無趣,如果你真的想看,程式碼在 GitHub 上。
避免使用 JavaScript
以下是圖片預覽用的 HTML 骨架:
<figure>
<div class="loader">
<object data="image.svg" type="image/svg+xml"></object>
<img class="frozen" src="data:image/png;base64,..." />
</div>
</figure>關鍵在於,把完整尺寸的圖片和預覽圖一起包在一個 loader 的 div 裡,並為它加上 width: auto 的 CSS 屬性:
.loader {
position: relative;
overflow: hidden;
width: auto;
}我把 SVG 包在 object 標籤裡,而不是用 img 元素。這樣的好處是,即使 SVG 載入失敗,也能顯示一個占位符。我把 object 定位在 loader 這個 div 的左上角。
.loader object {
position: absolute;
}
.loader img,
.loader object {
display: block;
top: 0;
left: 0;
width: 100%;
}以下是這個占位符 hack 的寫法,附上一些參考資料:
/* https://stackoverflow.com/a/29111371/270334 */
/* https://stackoverflow.com/a/32928240/270334 */
object {
position: relative;
float: left;
display: block;
&:after {
position: absolute;
top: 0;
left: 0;
display: block;
width: 1000px;
height: 1000px;
content: "";
background: #efefef;
}
}最後一部分是處理縮圖。和大多數網站一樣,我決定套用模糊濾鏡。在某種程度上,看起來就像圖片被凍結了一樣,所以我把 CSS 選擇器命名為 frozen。我還套用了縮放變形,讓邊緣更清晰。
.frozen {
-webkit-filter: blur(8px);
-moz-filter: blur(8px);
-o-filter: blur(8px);
-ms-filter: blur(8px);
filter: blur(8px);
transform: scale(1.04);
animation: 0.2s ease-in 0.4s 1 forwards fade;
width: 100%;
}
@keyframes fade {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}我用 CSS 動畫來取代 JavaScript。
動畫的持續時間是根據所有訪客中 95 百分位數的頁面載入時間來決定的。雖然只是個近似值,但對大多數讀者來說應該都適用。
成果
- 不需要 JavaScript
- 在所有現代瀏覽器上都能運作
- 主圖片無法載入時有備用方案
- 額外負擔極小
參考資源
隨機一篇部落格
留言
登入後參與討論