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 でホストされている特定の Web フォントをページで必要とする可能性が高い場合は、リソースの優先順位付けを利用できます。<link rel="preload"> を使用すると、CSSOM が作成されるのを待たずに、クリティカル レンダリング パスの早い段階で Web フォントへのリクエストをトリガーします。

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

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

フォントの読み込み中にテキストが見えないようにするという投稿では、デフォルトのブラウザの動作が一貫していないことがわかります。ただし、font-display を使用して、動作を指定できます。

対応ブラウザ

  • Chrome: 60.
  • Edge: 79.
  • Firefox: 58.
  • Safari: 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 フォント フェイスを定義および操作すること、それらのダウンロードの進行状況を追跡すること、デフォルトの遅延読み込み動作をオーバーライドすることのできるスクリプティング インターフェースを提供します。たとえば、特定の派生フォントが必要になることがわかっている場合に、その派生フォントを定義し、フォント リソースの取得を直ちに開始するようブラウザに指示することができます。

対応ブラウザ

  • Chrome: 35.
  • Edge: 79.
  • Firefox: 41。
  • Safari: 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 を使用している場合は、キャッシュ優先戦略でフォント リソースを提供することがほとんどのユースケースに適しています。

フォントを localStorage または IndexedDB を使って保存するべきではありません。これらには、独自のパフォーマンス問題があります。ブラウザの HTTP キャッシュが、ブラウザにフォント リソースを提供するための最も確実で最適なメカニズムです。

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

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

Lighthouse による WebFont の読み込み動作の自動テスト

Lighthouse を使用すると、ウェブフォント最適化のベスト プラクティスに沿ってサイトを構築するためのプロセスを自動化できます。

次の監査を行うと、ページがウェブフォント最適化のベスト プラクティスに沿って継続的に作成されていることを確認できます。