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

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

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

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

以下の監査は、ウェブフォントを最適化するためのおすすめの方法をページが継続的に実施していることを確認するのに役立ちます。