Chrome の新機能 Blink LazyLoad の要点と注意点

この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2018 の投稿記事です。

こんにちは。Webフロントエンドエンジニアの福井です。

最近 Chrome からブラウザネイティブの遅延読み込み機能である Blink Lazyload が試験的に公開されました。非常に便利である一方でいくつか気にしなければならない点があると感じたので、機能の要点と注意点をまとめたものを紹介します。

この機能は試験的な機能であり、仕様や振る舞いが変わる可能性があります。この記事の情報はあくまで執筆時点(2018/12/6)のものとしてご認識ください

参考

動作確認環境

OS
MacOS 10.14.1(Mojave)
Browser
Chrome 70.0.3538.110
Chrome Canary 72.0.3624.0

要点

通常 img, iframesrc が指定されると直ちに読み込みを開始しますが、それらがDOMツリー状に多量に配置されていた場合、同時に多量のリクエストが発生しページ読み込みのパフォーマンスを低下させることがあります。パフォーマンス低下を回避するため、画像の読み込みタイミングを img, iframe がスクロールなどで画面上に出てくるまで遅延させることがよくありますが、それをブラウザの機能として提供したものが Blink Lazyload です1)実際に動かしてみるとスクロールしなくても非表示部分の画像読み込みがなされることも多々あるので、画像の読み込みタイミングについては色々な条件を考慮していると思われます。またどの程度まで対象の画像に近づいたら読み込みを開始するかのしきい値はBlink LazyLoadによると実験して決めているようです

自前実装の遅延読み込みでは読み込んだ画像が表示された際に画像の表示エリアをあらかじめ確保できてなくてレイアウトがカクっとなることがありますが、 Blink LazyLoad ではImage Replacementにより適切なプレースホルダーを画面に配置するため、レイアウトのカクつきが抑制されます。

Demo

回線を絞って PSYENCE:MEDIA のトップページを読み込むとこのようになります。

ところどころ画像のファイルサイズが記載された四角いプレースホルダーが表示されて遅延読み込みが行われているのがわかると思います。これによりレイアウトを可能な限り維持して遅延読み込みをすることができます。プレースホルダー上に画像のファイルサイズが記載されていますが、これはレスポンスサイズを抑制したRange リクエストを先に発行しファイルサイズなど最低限の情報だけ取得しているためです。

使い方

Blink LazyLoad はデフォルトでは有効化されていないのでまずはアドレスバーに chrome://flags と入力し設定画面から機能を有効化する必要があります。

有効化するとすべての imgiframe が遅延読み込みの対象になりますが、個々のタグに lazyload 属性を指定して個別にコントロールすることもできます(後述しますが、個別指定は現時点では Chrome Canary のみです)。使い方はこれだけで非常にシンプルです。

<img src="/asset/img1.png" lazyload="off">
キーワード 説明
on 可視状態になるまで読み込みを遅延させる
off 読み込みを直ちに実行する
auto(default値) lazyload 属性をしていしない場合は auto がdefault値となり、ブラウザの挙動に従う。Chrome の場合は Blink LazyLoad の有効化を行うとブラウザの挙動は on となる

Blink LazyLoad は Chrome だけの独自機能となります。
現時点ではW3C の HTML 5.3 や proposal にも含まれていません。
whatwg への Pull Requestは出されているので whatwg の HTML Living Standard に入る可能性はあります。 HTMLの新たな仕様として受け入れられた場合は他のブラウザも同等の機能を実装する可能性があります。

注意点

display: none がついていると画像読み込み自体が発生しない

筆者が確認した限りでは img が遅延読み込み対象となっている場合に display: none がついていると可視状態と判断されず画像読み込み自体が開始されませんでした(正確には Range リクエストは発行されますがその後のメインリクエストが発行されません)。

よってdisplay: none を使って独自に遅延読み込みを実装している場合は注意が必要です。読み込みが開始されず意図しない挙動になる可能性があります。例として、以下のように display: none が指定された img を取得して load イベントが発火したタイミングで画像を表示しようとしても、 読み込みが開始されないので画像が表示されることはありません。

<img data-src="/asset/photo1.png" lazyload="on" style="display: none;" id="photo1">
var img = document.getElementById('photo1');
img.addEventListener('load', function() {
  // 読み込みが発生しないので以降が呼び出されることはない
  console.log('loaded!!'); 
  img.style = 'display: inline';
});
img.src = img.dataset.src;

似たようなスタイル指定として visibility: hidden がありますが、こちらは画像読み込みが発生しました。後者は前者と違って画面上に表示領域が確保されそれが空白として表示されるため Blink LazyLoad は可視状態と判断するようです。

JavaScriptで作った img は Blink LazyLoad 対象外

JavaScriptで作った img に関しては Blink LazyLoad 対象外となり、DOMツリーに配置されているかどうかや可視状態に関わらず src が指定されたタイミングで直ちに読み込みが開始されます。

Image elements created in JavaScript (i.e. "var img = new Image()")

LazyImages won't defer these images, since there's a higher chance that script on the page will wait until the image is loaded before adding the image to the page, but since the image isn't on the page, there's no way for the user to scroll down to it and trigger a full load of the image.

Blink LazyImages

以下のように imgdocument.createElement()で作成し src を指定しても img はまだ DOM ツリーにも配置されておらず当然不可視状態なので画像読み込みが開始されないように思われますが、実行すると画像読み込みが直ちに開始され読み込み完了後にコンソールに loaded!! と表示されます。

var createdImg = document.createElement('img');
createdImg.addEventListener('load', function() {
  console.log('loaded!!');
});
createdImg.src = '/assets/photo2.png';

この挙動により次のように考えることができます。

  • 表示する DOM ノードがほとんど JS で作られるシングルページアプリケーション(SPA)は Blink LazyLoad の影響を受ける可能性が低い
  • 逆に静的サイトなどある程度完成したHTMLをサーバーから返している場合は注意が必要
  • サーバーサイドレンダリング(SSR)もサーバーサイドで初期描画される HTML を生成するので影響を受ける可能性がある

キャッシュに画像があった場合は display: none でも画像が読み込まれる

👆で display: none がついていると画像読み込み自体が発生しない と書きましたが、筆者が動作確認した限りではキャッシュに画像がある場合はそこから画像読み込みが行われ、 load イベントも発火しました。キャッシュの有り無しで動作が変わるため、load イベントを利用する場合はキャッシュの考慮も必要です。

計測タグが意図通り動かない可能性がある

計測タグとして使われている img が Blink LazyLoad 対象だった場合、Range リクエストのみで完全なレスポンスが返ってきて問題なく動く可能性もありますが、レスポンスが不完全で意図しない動きになったり、続くメインリクエストと合わせて2回リクエストが飛ぶ2)display:none がついているとリクエストは Range リクエスト1回のみになりますことで計測が正確に行われない可能性があります。とりあえず lazyload="off" をつけて問題が起きないようにするのが良いかもしれません。

DOM の load イベントが画像読み込みがすべて完了する前に発火することがある

これまで DOM の load イベントはすべての画像読み込みが完了してから発火されていましたが Blink LazyLoad 機能を有効にしている場合は遅延読み込み対象の画像読み込みを待たなくなります。 DOM load 時に画像読み込みがすべて完了していることを前提にして何かしている場合は何かしら影響を受ける可能性があります。

個々の img への lazyload 指定が使えるのは現時点で Canary のみ

筆者が試した限りでは Chrome 70 で chrome://flags で Blink Lazyload を有効化することはできますが、個々の img タグへの lazyload 属性の指定は効いていないようでした(つまりすべての img が強制的に lazyload="on" 状態)。Chrome Canary では lazyload 属性を使って個別に off にすることができました。

画像が中途半端に表示されることがある

サーバーが Range リクエストのレスポンスが不十分なのに 200(OK) を返し、その不十分なデータを使って画像表示してしまうのが原因のようです。ブラウザ側でレスポンスコード以外でもチェックするようにするべきかもしれませんがデータが完全でない場合はサーバーが 206(Partial Content) を返すべきなので少し悩ましい問題のようです。

まとめ

ブラウザで遅延読み込みがサポートされるのは非常に便利である一方、読み込みの発生条件、キャッシュがある時の動きなど振る舞いにクセがありサイトが意図しない動作をする可能性もあることを紹介しました。今後この機能が正式に公開されたときに慌てないよう状況を引き続き見ていきたいと思います。

脚注

脚注
1 実際に動かしてみるとスクロールしなくても非表示部分の画像読み込みがなされることも多々あるので、画像の読み込みタイミングについては色々な条件を考慮していると思われます。またどの程度まで対象の画像に近づいたら読み込みを開始するかのしきい値はBlink LazyLoadによると実験して決めているようです
2 display:none がついているとリクエストは Range リクエスト1回のみになります