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)をサーバーにアドバタイズするようブラウザに指示します。

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

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

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

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

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

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

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

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

Service Worker でリソース選択を制御する

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

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])
    }
    ...
}
Client Hints Service Worker。

Service Worker では、クライアントサイドでリソースの選択を完全に制御できます。これは非常に重要です。可能性は無限大に近いので 考えてみましょう

  • ユーザー エージェントが設定した Client Hints ヘッダー値は書き換えることができます。
  • リクエストには、新しい Client Hints ヘッダー値を追加できます。
  • URL を書き換えて、画像リクエストを代替サーバー(CDN など)にポイントできます。
    • インフラストラクチャへのデプロイが容易になる場合は、ヒント値をヘッダーから URL 自体に移動することもできます。
  • レスポンスをキャッシュに保存し、リソースを提供する独自のロジックを定義できます。
  • ユーザーの接続状況に合わせてレスポンスを調整できます。
  • アプリケーションとユーザーの設定のオーバーライドを考慮できます。
  • 君は...心が望むことなら何でもできる。

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

Client Hints に関するよくある質問

  1. Client Hints はどこで利用できますか? Chrome 46 でリリース。FirefoxEdge で検討中です。

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

  3. Service Worker を使用している場合にクライアント ヒントが必要なのはなぜですか?Service Worker はレイアウト、リソース、ビューポートの幅の情報にアクセスできません。少なくとも、負荷のかかるラウンドトリップが発生し、画像リクエストが大幅に遅延する(例: 画像リクエストがプリロード パーサーによって開始されたとき)ことがないわけではありません。Client Hints はブラウザと統合され、このデータをリクエストの一部として利用できるようにします。

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

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

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

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