WebFont の読み込みとレンダリングを最適化する

Ilya Grigorik

「フル」な WebFont に、不要なスタイル上のすべてのバリエーションと、使用されない可能性のあるすべてのグリフを含めると、ダウンロードが数メガバイトに及ぶことがよくあります。この記事では、WebFonts の読み込みを最適化して、ユーザーが使用するものだけをダウンロードできるようにする方法を説明します。

@font-face CSS ルールは、すべてのバリアントを含むサイズの大きいファイルの問題に対処するために、フォント ファミリーをリソースのコレクションに分割できるように特別に設計されています。たとえば、Unicode のサブセットや、個別のスタイル バリエーションなどがあります。

これらの宣言により、ブラウザは必要なサブセットとバリアントを特定し、テキストのレンダリングに必要な最小限のセットをダウンロードします。これは非常に便利です。ただし、注意しないと、クリティカル レンダリング パスでパフォーマンスのボトルネックが生じ、テキストのレンダリングが遅れる可能性もあります。

デフォルトの動作

フォントの遅延読み込みには、テキストのレンダリングを遅延させる可能性のある、隠れた重要な意味があります。ブラウザは、テキストのレンダリングに必要なフォント リソースを認識する前に、DOM ツリーと CSSOM ツリーに依存するレンダリング ツリーを構築する必要があります。その結果、フォント リクエストは他の重要なリソースよりもかなり遅れて、リソースが取得されるまでブラウザでテキストのレンダリングがブロックされる可能性があります。

フォントのクリティカル レンダリング パス

  1. ブラウザが HTML ドキュメントをリクエストします。
  2. ブラウザが HTML レスポンスの解析と DOM の構築を開始します。
  3. ブラウザが CSS、JS などのリソースを検出してリクエストをディスパッチします。
  4. すべての CSS コンテンツを受け取ると、ブラウザは CSSOM を構築し、それを DOM ツリーと組み合わせてレンダリング ツリーを構築します。
    • ページ上の指定されたテキストのレンダリングに必要なフォント バリアントがレンダリング ツリーによって示された後に、フォント リクエストがディスパッチされます。
  5. ブラウザはレイアウトを実行し、コンテンツを画面にペイントします。
    • フォントがまだ利用できない場合、ブラウザはテキスト ピクセルをレンダリングできない可能性があります。
    • フォントが使用可能になると、ブラウザはテキスト ピクセルをペイントします。

レンダリング ツリーのビルド直後に行われるページ コンテンツの最初のペイントと、フォント リソースのリクエストの間の「競合」により、ブラウザがページ レイアウトをレンダリングしてもテキストが省略される「空白のテキストの問題」が発生します。

WebFonts をプリロードし、font-display を使用して使用できないフォントに対するブラウザの動作を制御することにより、フォントの読み込みによる空白のページとレイアウトの移動を防ぐことができます。

WebFont リソースをプリロードする

事前にわかっている URL でホストされている特定の WebFont がページで必要になる可能性が高い場合は、リソースの優先順位付けを利用できます。<link rel="preload"> を使用すると、CSSOM の作成を待つことなく、クリティカル レンダリング パスの早い段階で WebFont のリクエストがトリガーされます。

テキスト レンダリングの遅延をカスタマイズする

プリロードを行うと、ページのコンテンツがレンダリングされるときに WebFont が利用可能になる可能性が高くなりますが、そうする保証はありません。まだ利用できない font-family を使用するテキストをレンダリングするとき、ブラウザがどのように動作するかを考慮する必要があります。

フォントの読み込み時に非表示のテキストを避けるの投稿で、デフォルトのブラウザの動作が一貫していないことを説明しています。 最新のブラウザの動作を指定するには、font-display を使用します。

対応ブラウザ

  • 60
  • 79
  • 58
  • 11.1

ソース

一部のブラウザに実装されている既存のフォント タイムアウト動作と同様に、font-display ではフォント ダウンロードの有効期間を次の 3 つの主要な期間に分割します。

  1. 最初の期間は、フォント ブロックの期間です。この期間中にフォント フェースが読み込まれていない場合、それを使おうとする要素は、代わりに非表示のフォールバック フォント フェースでレンダリングする必要があります。ブロック期間中にフォント フェースが正常に読み込まれると、そのフォント フェースは通常どおりに使用されます。
  2. フォント スワップ期間は、フォント ブロック期間の直後に発生します。この期間中にフォント フェースが読み込まれていない場合、それを使おうとする要素は代わりにフォールバック フォント フェースでレンダリングする必要があります。スワップ期間中にフォント フェースが正常に読み込まれると、フォント フェースが通常どおりに使用されます。
  3. フォント障害期間は、フォント スワップ期間の直後に発生します。この期間の開始時にフォント フェースがまだ読み込まれていない場合、読み込みの失敗としてマークされ、通常のフォント フォールバックが発生します。それ以外の場合は、フォント フェースが通常どおり使用されます。

これらの期間を理解すると、font-display を使用して、ダウンロードの有無に応じてフォントのレンダリング方法を決定できます。

font-display プロパティを操作するには、@font-face ルールにプロパティを追加します。

@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 400;
  font-display: auto; /* or block, swap, fallback, optional */
  src: local('Awesome Font'),
       url('/fonts/awesome-l.woff2') format('woff2'), /* will be preloaded */
       url('/fonts/awesome-l.woff') format('woff'),
       url('/fonts/awesome-l.ttf') format('truetype'),
       url('/fonts/awesome-l.eot') format('embedded-opentype');
  unicode-range: U+000-5FF; /* Latin glyphs */
}

font-display は現在、次の値の範囲をサポートしています。

  • auto
  • block
  • swap
  • fallback
  • optional

フォントのプリロードと font-display プロパティの詳細については、次の記事をご覧ください。

Font Loading API

<link rel="preload"> と CSS の font-display を併用すると、オーバーヘッドを増やすことなく、フォントの読み込みとレンダリングを詳細に制御できます。ただし、追加のカスタマイズが必要で、JavaScript の実行によるオーバーヘッドの発生を許容できる場合は、別の方法があります。

Font Loading API は、CSS フォント フェースの定義と操作、ダウンロードの進行状況の追跡、デフォルトの遅延読み込み動作のオーバーライドを行うためのスクリプト インターフェースを提供します。たとえば、特定のフォント バリアントが必要であることが確実な場合は、それを定義して、フォント リソースの取得を直ちに開始するようブラウザに指示できます。

対応ブラウザ

  • 35
  • 79
  • 41
  • 10

ソース

var font = new FontFace("Awesome Font", "url(/fonts/awesome.woff2)", {
  style: 'normal', unicodeRange: 'U+000-5FF', weight: '400'
});

// don't wait for the render tree, initiate an immediate fetch!
font.load().then(function() {
  // apply the font (which may re-render text and cause a page reflow)
  // after the font has finished downloading
  document.fonts.add(font);
  document.body.style.fontFamily = "Awesome Font, serif";

  // OR... by default the content is hidden,
  // and it's rendered after the font is available
  var content = document.getElementById("content");
  content.style.visibility = "visible";

  // OR... apply your own render strategy here...
});

さらに、フォントのステータスを(check() メソッドで)確認し、ダウンロードの進行状況を追跡できるため、ページ上のテキストをレンダリングするためのカスタム戦略を定義することもできます。

  • フォントが使用可能になるまで、すべてのテキスト レンダリングを保留できます。
  • フォントごとにカスタムのタイムアウトを実装できます。
  • 代替フォントを使用してレンダリングのブロックを解除し、フォントが利用可能になったら、目的のフォントを使用する新しいスタイルを挿入できます。

何より素晴らしいのは、ページ上のさまざまなコンテンツについて、上記の戦略を組み合わせることができるという点です。たとえば、フォントが使用可能になるまで一部のセクションでテキスト レンダリングを遅らせて、代替フォントを使用し、フォントのダウンロードが完了した後に再レンダリングすることができます。

適切なキャッシュ保存が必須

フォント リソースは通常、頻繁に更新されない静的リソースです。そのため、max-age 有効期限が長い場合に適しています。条件付き ETag ヘッダーと、すべてのフォント リソースに最適な Cache-Control ポリシーの両方を指定してください。

ウェブ アプリケーションで Service Worker を使用している場合、ほとんどのユースケースでは、キャッシュ ファースト戦略でフォント リソースを提供するのが適切です。

localStorageIndexedDB を使用してフォントを保存しないでください。これらのそれぞれにパフォーマンス上の問題があります。ブラウザの HTTP キャッシュは、フォント リソースをブラウザに配信するための最も確実で最適なメカニズムです。

WebFont 読み込みチェックリスト

  • <link rel="preload">font-display、または Font Loading API を使用してフォントの読み込みとレンダリングをカスタマイズする: デフォルトの遅延読み込み動作では、テキスト レンダリングが遅れることがあります。これらのウェブ プラットフォーム機能を使用すると、特定のフォントに対してこの動作をオーバーライドし、ページ上のさまざまなコンテンツに対してカスタムのレンダリング方法とタイムアウト戦略を指定できます。
  • 再検証と最適なキャッシュ ポリシーを指定する: フォントは静的リソースであり、更新頻度は低くなります。異なるページ間でフォントを効率的に再利用できるように、サーバーで長期間の max-age タイムスタンプと再検証トークンを提供してください。Service Worker を使用している場合は、キャッシュ ファーストの戦略が適しています。

Lighthouse を使用した WebFont の読み込み動作の自動テスト

Lighthouse を使用すると、ウェブフォントの最適化に関するベスト プラクティスを確実に実践するプロセスを自動化できます。

以下の監査は、ウェブフォントの最適化に関するベスト プラクティスが継続的に適用されているかどうかを確認できます。