Client Hints を使用したリソース選択の自動化

ウェブ向けに構築することで、比類のないリーチを実現できます。ウェブ アプリケーションは、ブランドやプラットフォームに関係なく、スマートフォン、タブレット、ノートパソコン、デスクトップ、テレビなど、ほぼすべての接続デバイスでワンクリックで利用できます。最適なエクスペリエンスを提供するために、各フォーム ファクタの表示や機能に適応するレスポンシブ サイトを構築し、アプリケーションができるだけ迅速に読み込まれるようにパフォーマンス チェックリストを実行しました。クリティカル レンダリング パスを最適化し、テキスト リソースを圧縮してキャッシュに保存し、アカウントの大部分が転送された画像リソースを確認します。問題は、画像の最適化は難しいことです。

  • 適切な形式(ベクターとラスター)を決定する
  • 最適なエンコード形式(jpeg、webp など)を決定する
  • 適切な圧縮設定(非可逆圧縮と可逆圧縮)を決定する
  • 保持または削除するメタデータを決定する
  • ディスプレイと DPR の解像度ごとに複数のバリエーションを作成する
  • ...
  • ユーザーのネットワークの種類、速度、設定を考慮する

個別に見ると、これらはよく理解されている問題です。これらを総合すると、デベロッパーにとって見過ごされがちな、または無視されがちな大きな最適化の領域が生まれます。人間は、特に多くのステップが関係する場合、同じ検索空間を繰り返し探索するのが苦手です。一方、コンピュータはこの種の作業に優れています。

画像や、同様のプロパティを持つ他のリソースに対して、持続可能な最適化戦略を立てるには、自動化がシンプルな答えとなります。リソースを手作業で調整しているのであれば、間違いです。忘れて、怠け者になり、さもないと、誰かがこのような間違いを犯すでしょう。

パフォーマンスを重視するデベロッパーの物語

画像最適化空間の検索には、ビルド時と実行時の 2 つのフェーズがあります。

  • 適切な形式とエンコード タイプを選択する、各エンコーダの圧縮設定を調整する、不要なメタデータを削除するなど、一部の最適化はリソース自体に固有のものです。これらの手順は「ビルド時」に実行できます。
  • その他の最適化は、リクエストするクライアントのタイプとプロパティによって決定され、「ランタイム」に実行する必要があります。クライアントの DPR と目的のディスプレイ幅に適したリソースを選択し、クライアントのネットワーク速度、ユーザーとアプリケーションの設定などを考慮します。

ビルド時のツールはありますが、改善の余地があります。たとえば、各画像と各画像形式の「品質」設定を動的に調整することで、多くの節約効果が得られます。しかし、研究以外で実際に使用している例はまだ見当たりません。これはイノベーションに適した分野ですが、この投稿ではそのままにしておきます。ストーリーの実行時に発生する部分に注目しましょう。

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

アプリケーションの意図は非常にシンプルです。ユーザーのビューポートの 50% に画像を取得して表示します。ほとんどのデザイナーはここで手を洗ってからバーに向かいます。一方、パフォーマンスに気を配っているチームのデベロッパーは、長い夜を過ごすことになります。

  1. 最適な圧縮率を得るために、各クライアントに最適な画像形式(Chrome には WebP、Edge には JPEG XR、それ以外には JPEG)を使用することを考えています。
  2. 最適な画質を得るには、1x、1.5x、2x、2.5x、3x などの異なる解像度で、各画像の複数のバリエーションを生成する必要があります。
  3. 不要なピクセルを配信しないようにするには、「ユーザーのビューポートの 50%」が実際に何を意味するのかを理解する必要があります。ビューポートの幅はさまざまです。
  4. また、低速ネットワークのユーザーに対しては、自動的に低解像度を取得する復元力のあるエクスペリエンスを提供することも理想的です。結局のところ、次はグラスを飲みます。
  5. また、アプリケーションには、取得する画像リソースに影響するユーザー コントロールも公開されているため、その点も考慮する必要があります。

デザイナーは、ビューポートのサイズが小さい場合は読みやすさを最適化するために、別の画像を幅 100% で表示する必要があることに気付きました。つまり、もう 1 つのアセットに対して同じプロセスを繰り返し、ビューポートのサイズに応じて取得する必要があります。ここまでで、この作業が難しいことをお伝えしましたか?では、始めましょう。picture 要素を使用すると、かなりの結果が得られます

<picture>
    <!-- serve WebP to Chrome and Opera -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
        /image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
        /image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
    type="image/webp">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
        /image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
        /image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
    type="image/webp">
    <!-- serve JPEGXR to Edge -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
        /image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
        /image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
        /image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
        /image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <!-- serve JPEG to others -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
        /image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
        /image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
        /image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
        /image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
    <!-- fallback for browsers that don't support picture -->
    <img src="/image/thing.jpg" width="50%">
</picture>

アートディレクションとフォーマットの選択を行い、クライアントのデバイスの DPR とビューポート幅のばらつきに対応するため、各画像の 6 つのバリエーションを用意しました。すばらしいですね!

残念ながら、picture 要素では、クライアントの接続タイプや速度に基づいて動作するルールを定義することはできません。ただし、処理アルゴリズムによっては、ユーザー エージェントが取得するリソースを調整できる場合もあります(ステップ 5 を参照)。ユーザー エージェントが十分に賢いことを祈るしかありません。(注: 現在の実装はどれも対応していません)。同様に、picture 要素には、アプリやユーザーの設定を考慮したアプリ固有のロジックを許可するフックがありません。最後の 2 ビットを取得するには、上記のロジックをすべて JavaScript に移動する必要がありますが、その場合、picture が提供するプリロード スキャナの最適化は無効になります。うーん、わかりました。

これらの制限を除けば、機能します。少なくとも、この特定のアセットについてはそうです。長期的な課題は、デザイナーやデベロッパーがすべてのアセットに対してこのようなコードを手作業で作成することは期待できないことです。最初は楽しい頭の体操ですが、すぐに魅力が失われます。自動化が必要ですIDE やその他のコンテンツ変換ツールを使用すると、上記のボイラープレートを自動的に生成できます。

クライアント ヒントによるリソース選択の自動化

深呼吸をして不信感をいったん捨て、次の例について考えてみましょう。

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
    <source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
    <img sizes="100vw" src="/image/thing-crop">
</picture>

上記の例は、上記の長い画像マークアップと同じ機能をすべて提供するのに十分です。また、後述するように、画像リソースの取得方法、取得する画像、取得するタイミングをデベロッパーが完全に制御できます。最初の行が「魔法」です。この行は、クライアント ヒント レポートを有効にし、デバイスのピクセル比率(DPR)、レイアウト ビューポートの幅(Viewport-Width)、リソースの想定表示幅(Width)をサーバーに通知するようブラウザに指示します。

クライアント ヒントを有効にすると、生成されるクライアントサイド マークアップには表示要件のみが保持されます。デザイナーは、画像タイプ、クライアントの解像度、配信バイト数を減らすための最適なブレークポイント、その他のリソース選択基準を気にする必要はありません。現実を直視しますが 彼らが実際にやったことはなかったのでさらに、実際のリソース選択はクライアントとサーバーでネゴシエートされるため、デベロッパーは上記のマークアップを書き換えて拡張する必要もありません。

Chrome 46 では、DPRWidthViewport-Width ヒントをネイティブにサポートしています。ヒントはデフォルトで無効になっています。上記の <meta http-equiv="Accept-CH" content="..."> は、指定されたヘッダーを送信リクエストに追加するよう Chrome に指示するオプトイン シグナルとして機能します。準備ができたら、サンプル画像リクエストのリクエスト ヘッダーとレスポンス ヘッダーを確認しましょう。

Client Hints ネゴシエーションの図

Chrome は、Accept リクエスト ヘッダーで WebP 形式のサポートをアドバタイズします。同様に、新しい Edge ブラウザは Accept ヘッダーで JPEG XR のサポートをアドバタイズします。

次の 3 つのリクエスト ヘッダーは、クライアントのデバイスのデバイス ピクセル比(3x)、レイアウト ビューポートの幅(460 ピクセル)、リソースの意図した表示幅(230 ピクセル)をアドバタイズするクライアント ヒント ヘッダーです。これにより、独自のポリシーセット(事前生成されたリソースの可用性、リソースの再エンコードまたはサイズ変更のコスト、リソースの人気度、現在のサーバー負荷など)に基づいて最適なイメージ バリアントを選択するために必要なすべての情報がサーバーに提供されます。この場合、サーバーは DPR ヒントと Width ヒントを使用して、Content-TypeContent-DPRVary ヘッダーで示されているように WebP リソースを返します。

魔法のような方法はありません。リソースの選択を HTML マークアップから、クライアントとサーバー間のリクエスト / レスポンス ネゴシエーションに移動しました。その結果、HTML は表示要件のみを考慮し、デザイナーやデベロッパーであれば誰でも記述できます。一方で、画像最適化空間の検索はコンピュータで行われるようになり、現在では大規模に容易に自動化できるようになっています。パフォーマンスを重視するデベロッパーを思い出してください。次に、提供されたヒントを活用して適切なレスポンスを返す画像サービスを作成します。任意の言語またはサーバーを使用できます。また、サードパーティ サービスまたは CDN に代行してもらうこともできます。

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

また、上のこの男性を覚えていますか?クライアント ヒントを使用すると、追加のマークアップなしで、単純な画像タグが DPR、ビューポート、幅を認識できるようになります。アート ディレクションを追加する必要がある場合は、前述のように picture タグを使用できます。そうしないと、既存のイメージタグがすべてスマートになりました。クライアント ヒントは、既存の img 要素と picture 要素を強化します。

サービス ワーカーによるリソース選択の制御

ServiceWorker は、実質的にはブラウザで実行されるクライアントサイド プロキシです。すべての送信リクエストをインターセプトし、レスポンスを検査、書き換え、キャッシュに保存、合成することもできます。画像も同様で、クライアント ヒントを有効にすると、アクティブな ServiceWorker は画像リクエストを識別し、提供されたクライアント ヒントを検査して、独自の処理ロジックを定義できます。

self.onfetch = function(event) {
    var req = event.request.clone();
    console.log("SW received request for: " + req.url)
    for (var entry of req.headers.entries()) {
    console.log("\t" + entry[0] +": " + entry[1])
    }
    ...
}
クライアントが serviceWorker をヒントします。

ServiceWorker を使用すると、リソースの選択をクライアントサイドで完全に制御できます。これは非常に重要です。可能性は無限に近いため、よく理解してください。

  • ユーザー エージェントによって設定されたクライアント ヒント ヘッダー値を書き換えることができます。
  • リクエストには、新しい Client Hints ヘッダー値を追加できます。
  • URL を書き換えて、画像リクエストを代替サーバー(CDN など)に転送できます。
    • インフラストラクチャに簡単にデプロイできるように、ヒント値をヘッダーから URL 自体に移動することもできます。
  • レスポンスをキャッシュに保存し、リソースを提供する独自のロジックを定義できます。
  • ユーザーの接続状況に応じてレスポンスを調整できます。
  • アプリケーションとユーザーの設定のオーバーライドを考慮できます。
  • 本当に何でもできます。

picture 要素は、HTML マークアップで必要なアートディレクション制御を提供します。クライアントのヒントは、結果として得られる画像リクエストにアノテーションを提供し、リソース選択の自動化を可能にします。ServiceWorker は、クライアントでリクエストとレスポンスを管理する機能を提供します。これは、拡張可能なウェブの実例です。

クライアント ヒントに関するよくある質問

  1. クライアント ヒントはどこで利用できますか?Chrome 46 でリリースされました。FirefoxEdge で検討中。

  2. クライアント ヒントがオプトインなのはなぜですか?クライアント ヒントを使用しないサイトのオーバーヘッドを最小限に抑えたいと考えています。クライアント ヒントを有効にするには、ページ マークアップに Accept-CH ヘッダーまたは同等の <meta http-equiv> ディレクティブを指定する必要があります。どちらか一方が存在する場合、ユーザー エージェントはすべてのサブリソース リクエストに適切なヒントを追加します。将来的には、特定のオリジンに対してこの設定を維持する追加のメカニズムを提供する可能性があります。これにより、ナビゲーション リクエストで同じヒントを配信できるようになります。

  3. ServiceWorker がある場合、クライアント ヒントが必要な理由Service Worker はレイアウト、リソース、ビューポートの幅の情報にアクセスできません。少なくとも、コストの高いラウンドトリップを導入し、画像リクエストを大幅に遅らせることなく、(画像リクエストがプリロード パーサーによって開始された場合など)。クライアント ヒントはブラウザと統合され、このデータをリクエストの一部として利用できるようにします。

  4. クライアント ヒントは画像リソース専用ですか?DPR、Viewport-Width、Width のヒントの主なユースケースは、画像アセットのリソース選択を可能にすることです。ただし、タイプに関係なく、すべてのサブリソースに同じヒントが配信されます。たとえば、CSS リクエストと JavaScript リクエストも同じ情報を取得し、それらのリソースの最適化にも使用できます。

  5. 一部の画像リクエストで幅が報告されないのはなぜですか?サイトが画像の固有のサイズに依存しているため、ブラウザが意図した表示幅を認識していない可能性があります。そのため、このようなリクエストや、「表示幅」がないリクエスト(JavaScript リソースなど)では、幅のヒントは省略されます。幅のヒントを受け取るには、画像にサイズ値を指定してください。

  6. <お気に入りのヒントを挿入> の場合はどうなりますか? ServiceWorker を使用すると、デベロッパーは送信されるすべてのリクエストをインターセプトして変更(新しいヘッダーの追加など)できます。たとえば、現在の接続タイプを示す NetInfo ベースの情報を簡単に追加できます。ServiceWorker での機能に関するレポートをご覧ください。Chrome に搭載されている「ネイティブ」ヒント(DPR、Width、Resource-Width)は、純粋な SW ベースの実装ではすべての画像リクエストが遅延するため、ブラウザに実装されています。

  7. 詳細情報やデモはどこで確認できますか?説明ドキュメントをご確認ください。フィードバックやその他の質問がある場合は、お気軽に GitHub で問題を報告してください。