使用純 CSS 與 LQIP 實現閃電般快速的影像預覽
我的網站速度還算快。
我希望無論使用何種裝置或身在何處,每次載入頁面都能感覺靈敏輕快。這應該不令人意外,畢竟我只是使用純粹的 HTML 和 CSS。只要可行,我都會盡量避免使用 JavaScript。
還有一件事讓我相當困擾:圖片載入後造成的版面重排。
問題在於,當文字準備好要顯示時,圖片的尺寸仍是未知的。結果就是,一旦上方有圖片載入,文字就會在螢幕上被往下推擠。
此外,在圖片載入期間,不會有任何預覽,只有空白區塊。以下是在較慢連線下的呈現情況:

我本可以透過寫死圖片的寬度和高度來解決這個問題,但那既繁瑣又容易出錯。而且還是沒有預覽效果。所以我開始好奇,其他人都是怎麼做的。🤔
極小的影像縮圖
我隱約記得,Facebook 在其行動應用程式中使用極小的預覽縮圖。他們從 JPEG 檔頭中擷取量化表來算繪預覽。這項資訊儲存在用戶端,因此不需要每次都下載。可惜,這種做法需要完全掌控影像編碼器。這對應用程式可行,但對網站來說幾乎行不通。
搜尋仍在繼續。
直到我的同事 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 位元組。
我已將縮圖製作流程整合到部落格的建置工具鏈中。實際用來產生影像的程式碼相當乏味。如果你真的想看,它就在 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%;
}以下是包含部分參考資料的預留位置小技巧:
/* 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 {
-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
- 在所有現代瀏覽器上皆可運作
- 在主影像無法載入時提供備援
- 額外負擔極小
參考資源
隨機一篇部落格