Unformed Building

display:noneな画像のダウンロードを避けるために

公開:
更新:

パーマリンク

img要素で読み込む画像は、CSSでdisplay: noneが指定されていたとしてもダウンロードされることはご存知かと思います。
それをどうにかしたいという話です。

前提として、画面サイズに応じたデザインの変更による画像表示の切り替えを想定しているわけではありません。
それにも対応できますが、使いにくいと思います。

結論から言うと「url()関数を指定したcontentプロパティをインラインスタイルで指定する」、これだけです。

なんでこんなことをしたかったのか

大量の小さな画像が配置されたウェブページを作っていましたが、それらの大半はユーザーが操作しない限り表示されないUIでした。
表示されるかどうか分からない画像をダウンロードさせるのもイマイチな気がして、ダウンロードを遅らせたかったというのが理由です。

通常のimg指定

デモを作ってありますので、確認してみましょう。
display: noneになっているのは親要素ですが、自身につけても変わりません。

p {
  display: none;
  padding: 10px;
  overflow: auto;
  resize: both;
  background-color: gainsboro;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}
<button id="button">画像を表示する</button>
<div id="container" class="container">
  <img src="./long-tailed-blue.jpg"
       class="image"
       width="2048"
       height="1536"
       alt="写真:ウラナミシジミ">
</div>

<script>
const button = document.querySelector('#button');
button.addEventListener('click', function() {
  const container = document.querySelector('#container');
  container.style.display = 'block';
  this.disabled = true;
});
</script>

デモページで開発者ツールを開き、ネットワークパネルを見てもらうと分かるように、ページを開いた時点で画像がダウンロードされています。
これを、ボタンが押下されて.containerdisplay: blockになったときにダウンロードさせたいというわけです。

表示されてからダウンロードを開始する

ご存知のように、CSSのbackground-imageで指定された背景画像は、対象がdisplay: noneになっているとダウンロードされません。
これはbackground-imageというより<image>型で扱われるリソースがそうなっているのではないかと思うのですが、ちょっとソースが分かりませんでした。CSS Images Moduleあたりかなと思ったんですが、仕様ではなく実装のほうなのかもしれません。

ともかく、背景画像にすればdisplay: noneのときにダウンロードされないようにはできるのですが、非常に不便です。
背景画像を使うということは、該当要素のサイズ指定が必要になりますが、そのためには画像のサイズを知っていなければなりません。

そこでcontentプロパティによる要素置換を使います。

デモをご覧ください
画像として扱う要素がimgからdivになっている以外は同じです。

<div id="container" class="container">
  <div style="content: url(./long-tailed-blue.jpg)"
       class="image">
  </div>
</div>

開発者ツールのネットワークパネルを見ると、ボタンを押下してdisplay: none.cotainerdisplay: blockになって初めて画像がダウンロードされているのが分かります。

.cotainerをリサイズすると、それに合わせて画像になっているdivのサイズも変わります。これはimg要素で読み込んだときと同じ動作です。

読み上げ対応

contentプロパティに<image>型が指定された要素は、アクセシビリティツリー上では画像として扱われます。
開発者ツールで確認してみてください。
こういう点では問題ありません。

しかしdivにはalt属性が使えませんので、代替テキストが指定できません。
この問題に対しては、次のように解決されることが望ましいでしょう。

content: url(./long-tailed-blue.jpg) / "写真:ウラナミシジミ"

contentプロパティ値の/(スラッシュ)で区切ったあとの文字列は代替テキストとなります。
構文的には次のようなものです。(2021年5月17日版 Editor’s Draft)

normal | none | [ <content-replacement> | <content-list> ] [/ [ <string> | <counter> ]+ ]? 

しかし、2021年5月現在、スラッシュ後の文字列に対応している環境はBlinkエンジンのみです。GeckoやWebkitでは使用できません。

対応している環境、たとえばGoogle Chromeを使って読み上げてみると、ちゃんと画像であることを伝えてくれます。
そうではない環境では、不正なCSSとしてcontentプロパティ自体が無視され、画像すら表示されません。

対処方法としては、替わりにaria-labelを使うことでしょう。

<div style="content: url(./long-tailed-blue.jpg)"
     class="image"
     aria-label="写真:ウラナミシジミ">
</div>

両者を比較するデモをご覧ください
このデモを使って開発者ツールを確認すると、ここまでの説明がわかりやすいかと思います。

問題点

img要素ではありませんので、widthまたはheight属性が使えません。つまり、画像のロードが完了しないとレイアウトが完了しません。
ただし、そのような場合は画像のサイズが分かっていることが前提ですので、CSSで指定すればいいでしょう。

.image {
  max-width: 100%;
  height: auto;
}
<div style="
       content: url(./long-tailed-blue.jpg);
       aspect-ratio: 2048 / 1536;
     "
     class="image"
     aria-label="写真:ウラナミシジミ">
</div>

また、img要素ではないことから、loading属性が使えません。
そもそもこの方法を採用するのは特殊なケースかと思いますので、ビューポート外の画像を遅延ロードさせたいなら普通にimg要素を使ったほうがいいのではないかと思います。

レスポンシブ画像はCSSのimage-set()関数を使えば解決できそうです。

これはあくまでもdivですので、コンテキストメニューが画像用のものにはなりません。
その点は解決できないでしょう。

loading="lazy"じゃ駄目なの?

それでもいいです。
ただ、現在はWebkitで使えないことは念頭に置いておく必要があるでしょう。
https://bugs.webkit.org/show_bug.cgi?id=196698がWebkitの対象バグです。

まとめ

わたしが使ったケースとしては、タブが20個あるタブUIで、そのコンテンツはリンク集であり、各リンクに小さな画像がついているというものでした。
タブUIの中身というものは、表示されるかどうか不明という意味では代表的なものかもしれません。
他にはdetails要素でしょうか。

ほとんどの場合、img要素を使うべきだと思いますが、このような方法もあると知っていれば役に立つことがあるかもしれません。