一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

preloadで画像の表示速度を改善する

宿泊事業本部フロントエンドエンジニアの宇都宮です。先日、ホテルリストページの高速化に関する記事を書きましたが、Resource Hintsのpreloadを利用することで、さらに高速化できました。そこで、preloadによる画像読み込みの最適化方法を紹介します。

以前の記事はこちら:

一休.comホテルリストの表示速度を従来比2倍にしました - 一休.com Developers Blog

また、今回改善対象としたページには下記URLからアクセスできます(スマホでアクセスするか、PCからの場合はUAを偽装する必要があります)。

https://www.ikyu.com/sd/tokyo/140000/

改善前

f:id:ryo-utsunomiya:20190227154455p:plain:w480
PageSpeed Insights: 改善前

f:id:ryo-utsunomiya:20190227155059p:plain:w480
Calibre: 改善前

改善後

f:id:ryo-utsunomiya:20190304172545p:plain:w480
PageSpeed Insights: 改善後

f:id:ryo-utsunomiya:20190304175204p:plain:w480
Calibre: 改善後

今回の改善のターゲットは、施設の画像を早く取得して、画面の主要部分を素早く描画することです。したがって、見るべき指標はSpeed Indexで、ここは約0.7秒改善しています!

多少のブレはありますが、PageSpeed Insights/WebPageTest/Calibreでの複数回の計測でいずれも改善という結果だったので、画像のpreloadは「効果あり」と見てよいと思います。

やったこと

改善前のリストページでは、ページ表示の最終段階で、各施設の大きめの画像を取得するリクエストが走っていました。

f:id:ryo-utsunomiya:20190301172328p:plain:w480
改善前の施設画像取得リクエスト

このようになっている理由は、施設画像の取得リクエストが走るまでに以下のステップを踏む必要があったからです。

  1. 検索APIのレスポンス取得
  2. 検索結果のレンダリング完了
  3. lazyloadの発火

そこで、検索APIのレスポンス取得が終わったタイミングで、施設画像のpreloadを行っては? と思い、実装してみました。

preloadは、リソースの取得処理が実際に発火するよりも先に、ブラウザに「将来このリソースを取得します」と教えることで、ブラウザがリソースを先読みしてキャッシュできるようにする機能です。

developer.mozilla.org

具体的には、HTMLの <link rel="preload"> という要素にpreloadしたいリソースの種別(as)とURL(href)を書いておくと、ブラウザがこのリソースを先に読んでおいてくれます。

JavaScriptでpreloadを実行する場合、以下のような実装になると思います。preloadをサポートしているのはiOS 11.3以上なので、feature detectionは必須です。

// preloadのfeature detection
const supportsPreload = (() => {
  try {
    return document.createElement('link').relList.supports('preload');
  } catch (e) {
    return false;
  }
})();

/**
 * 指定したリソースをpreloadする
 * @param {string} href
 * @param {string} as
 */
function preload(href, as) {
  if (!supportsPreload) return;

  const link = document.createElement('link');
  link.setAttribute('rel', 'preload');
  link.setAttribute('as', as);
  link.setAttribute('href', href);
  link.onload = () => document.head.removeChild(link);
  document.head.appendChild(link);
}

/**
 * 画像をpreloadする
 * @param {string} href
 */
function preloadImage(href) {
  preload(href, 'image');
}

preloadの呼び出し側はこんな感じです。

// ファーストビューに入る施設の画像をpreload
searchResult.accommodationList
  .slice(0, Math.round(window.innerHeight / 300))
  .forEach(a => preloadImage(a.imageUrl));

これによって、以下のように、preloadした画像が優先的に読み込まれるようになりました。

f:id:ryo-utsunomiya:20190304173712p:plain:w480
改善後の施設画像取得リクエスト

この結果、ファーストビューが完全に描画されるまでの時間が短くなりました。

なお、まだviewportに入っていない施設の画像は従来通りlazyloadしているため、リクエストの終盤になっています。

「大きめの画像を全てpreloadする」 vs 「ファーストビューで見える画像のみpreloadする」で比較すると、後者の方が低速回線時のSpeed Indexが良くなったため、ファーストビューで見える画像のみpreloadしています。

まとめ

今回のように、大きめの画像をページ表示の後半で取得しているような場合には、preloadによって一定のパフォーマンス改善効果が得られることがわかりました。 preloadすることでパフォーマンスが改善されるかはアプリケーションの要件次第ですが、簡単に実装できるので、引き出しに入れておくと良いと思います。