Pure CSSとLQIPによる超高速な画像プレビュー
原文は Matthias Endler により に公開されました。 このブログを購読する
私のウェブサイトはそれなりに高速です。
どの端末から、どこからアクセスしても、すべてのページがキビキビと読み込まれるようにしたいと思っています。驚くことではないかもしれません。なにしろ、ただの素のHTMLとCSSだけで作っているのですから。JavaScriptは可能な限り避けています。
ただ一つ、ずっと気になっていたことがありました。画像が読み込まれたあとにレイアウトがずれることです。
問題は、テキストを表示する準備ができた時点では、まだ画像の寸法がわからないことにあります。そのため、上にある画像が読み込まれた瞬間に、テキストが画面の下方向へ押し出されてしまうのです。
また、画像の読み込み中はプレビューも表示されず、空白がぽっかり空いたままになります。低速な回線では次のように見えます。

画像の幅と高さをハードコーディングすれば直せますが、面倒ですしミスも起こりやすくなります。しかもプレビューは表示されません。そこで、他の人はどうしているのだろうと考えました。🤔
小さなサムネイル画像
ぼんやりと覚えていたのですが、Facebookはモバイルアプリで極小のプレビュー用サムネイルを使っているという話です。JPEGヘッダーから量子化テーブルを抽出してプレビューを描画するそうです。この情報はクライアント側に保存されるため、毎回ダウンロードする必要がありません。残念ながら、この手法は画像エンコーダーを完全に制御できることが前提です。アプリならともかく、ウェブサイトではほとんど使えません。
探し続けました。
そんなとき、同僚のTobias BaldaufがLQIP (Low-Quality Image Placeholders)を教えてくれました。
アイデアは次のとおりです。
- 低画質のサムネイル画像をインラインで埋め込んだ状態でページを読み込む。
- ページが完全に読み込まれたら(たとえば
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は2番目に小さかったのですが、画像をリサイズしてぼかしフィルターをかけたときの仕上がりに満足できませんでした。
最終的には、サイズと品質のバランスに優れた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でラップし、そのdivにwidth: autoというCSS属性を指定することです。
.loader {
position: relative;
overflow: hidden;
width: auto;
}SVGはimg要素ではなくobjectタグでラップしています。そうすることで、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;
}
}最後のパートはサムネイルの扱いです。他の多くのサイトと同様に、ぼかしフィルターをかけることにしました。どこか画像がfrozen(凍結)したように見えるので、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;
}
}JavaScriptの代わりにCSSアニメーションを使っています。
アニメーションの長さは、ページを訪れたすべてのユーザーの95パーセンタイルの読み込み時間に基づいています。あくまで近似値ですが、ほとんどの読者にはうまく機能するはずです。
結果
- JavaScript不要
- すべてのモダンブラウザで動作
- メイン画像の読み込みに失敗した場合のフォールバックに対応
- オーバーヘッドはごくわずか
参考資料
記事をランダムに読む
コメント
ログインしてコメントする