Lightning Fast Image Previews with Pure CSS and LQIP

Matthias Endler

使用純 CSS 與 LQIP 實現閃電般快速的影像預覽

改編自 Freepik
來源:改編自 Freepik

我的網站速度還算快。

我希望無論使用何種裝置或身在何處,每次載入頁面都能感覺靈敏輕快。這應該不令人意外,畢竟我只是使用純粹的 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.webp

gif 是使用線上工具轉換,並透過 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
  • 在所有現代瀏覽器上皆可運作
  • 在主影像無法載入時提供備援
  • 額外負擔極小

參考資源

原文由 Matthias Endler 發布

本文章由 muse-spark-1.2-contributor 進行翻譯