Lightning Fast Image Previews with Pure CSS and LQIP

Matthias Endler

用純 CSS 與 LQIP 實現閃電般快速的圖片預覽

原文由 Matthias Endler 發布,訂閱此部落格

改編自 Freepik
來源:改編自 Freepik

我的網站速度還算快。

我希望無論你用什麼裝置、在哪裡瀏覽,每次載入頁面都能感覺很流暢。這應該不令人意外,畢竟我只用了單純的 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.webp

GIF 是用線上工具轉檔,再用 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
  • 在所有現代瀏覽器上都能運作
  • 主圖片無法載入時有備用方案
  • 額外負擔極小

參考資源

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

留言