フォント フォールバックのフレームワーク ツール

Janicklas Ralph James
Janicklas Ralph James

font-display: swap を使用してフォントを読み込むサイトでは、ウェブフォントが読み込まれて代替フォントでスワップされたときにレイアウト シフト(CLS)が発生することがよくあります。

CLS を防ぐには、代替フォントの大きさをメインフォントの大きさに合わせて調整します。@font-face ルールの size-adjustascent-overridedescent-overrideline-gap-override などのプロパティを使用すると、代替フォントの指標をオーバーライドできます。これにより、デベロッパーはフォントの表示方法を詳細に制御できます。font-fallback とオーバーライド プロパティの詳細については、こちらの投稿をご覧ください。この手法の実装例については、こちらのデモをご覧ください。

この記事では、Next.js フレームワークと Nuxt.js フレームワークでフォントサイズの調整を実装して、代替フォント CSS を生成し、CLS を削減する方法について説明します。また、Fontaine や Capsize などのクロスカット ツールを使用して代替フォントを生成する方法についても説明します。

背景

font-display: swap は通常、FOIT(非表示テキストのフラッシュ)を防ぎ、コンテンツを画面にすばやく表示するために使用されます。swap の値は、そのフォントを使用したテキストをシステム フォントを使用してすぐに表示し、カスタム フォントが準備できた場合にのみシステム フォントを置き換えるようにブラウザに指示します。

swap の最大の問題は、2 つのフォントの文字サイズの違いによって画面のコンテンツがずれるという、耳障りな効果です。これにより、特にテキストが多いウェブサイトでは CLS スコアが低下します。

次の画像は、問題の例を示しています。最初の画像では font-display: swap が使用されており、代替フォントのサイズは調整されていません。2 つ目は、CSS @font-face ルールを使用してサイズを調整することで、読み込みエクスペリエンスがどのように改善されるかを示しています。

フォントサイズを調整しない

body {
  font-family: Inter, serif;
}
フォントとサイズが突然変化し、不快な効果を生むテキスト。

フォントサイズを調整した後

body {
  font-family: Inter, fallback-inter, serif;
  }

@font-face {
  font-family: "fallback-inter";
  ascent-override: 90.20%;
  descent-override: 22.48%;
  line-gap-override: 0.00%;
  size-adjust: 107.40%;
  src: local("Arial");
}
別のフォントにスムーズに移行するテキスト。

代替フォントサイズを調整することは、フォント読み込み時のレイアウト シフトを防ぐ効果的な方法ですが、フォント フォールバックに関するこちらの投稿で説明されているように、ロジックをゼロから実装するのは難しい場合があります。幸い、アプリの開発中にこれを簡単に行うためのツールがすでにいくつかあります。

Next.js でフォントのフォールバックの最適化する方法

Next.js には、フォールバック フォントの最適化を有効にする組み込みの方法が用意されています。@next/font コンポーネントを使用してフォントを読み込むと、この機能がデフォルトで有効になります。

@next/font コンポーネントは、Next.js バージョン 13 で導入されました。このコンポーネントには、Google フォントまたはカスタム フォントをページにインポートするための API が用意されており、フォント ファイルの自動セルフホスティングが組み込まれています。

使用すると、代替フォント メトリックが自動的に計算され、CSS ファイルに挿入されます。

たとえば、Roboto フォントを使用している場合は、通常、次のように CSS で定義します。

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}

body {
  font-family: Roboto;
}

next/font に移行するには:

  1. 「next/font」から「Roboto」関数をインポートして、Roboto フォント宣言を JavaScript に移動します。関数の戻り値は、コンポーネント テンプレートで利用できるクラス名になります。この機能を有効にするには、構成オブジェクトに display: swap を追加してください。

     import { Roboto } from '@next/font/google';
    
    const roboto = Roboto({
      weight: '400',
      subsets: ['latin'],
      display: 'swap' // Using display swap automatically enables the feature
    })
    
  2. コンポーネントで、生成されたクラス名 javascript export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en" className={roboto.className}> <body>{children}</body> </html> ); } を使用します。

adjustFontFallback 構成オプション:

@next/font/google の場合: 累積レイアウト シフトを軽減するために自動フォールバック フォントを使用するかどうかを設定するブール値。デフォルトは true です。Next.js は、フォントタイプ(それぞれセリフとサンセリフ)に応じて、代替フォントを Arial または Times New Roman に自動的に設定します。

@next/font/local の場合: 自動フォールバック フォントを使用して累積レイアウト シフトを低減するかどうかを設定する文字列またはブール値 false。指定できる値は ArialTimes New Romanfalse です。デフォルトは Arial です。セリフ フォントを使用する場合は、この値を Times New Roman に設定することを検討してください。

Google Fonts のもう一つのオプションは

next/font コンポーネントを使用できない場合は、optimizeFonts フラグを使用する方法でも、Google Fonts でこの機能を使用できます。Next.js では、OptimizeFonts 機能がデフォルトで有効になっています。この機能により、Google フォント CSS が HTML レスポンスにインライン化されます。さらに、次のスニペットに示すように、next.config.js で experimental.adjustFontFallbacksWithSizeAdjust フラグを設定することで、フォントのフォールバック調整機能を有効にできます。

// In next.config.js
module.exports = {
 experimental: {
   adjustFontFallbacksWithSizeAdjust: true,
 },
}

: 新しく導入された app ディレクトリではこの機能をサポートする予定はありません。長期的には next/font を使用することをおすすめします。

Nuxt でフォント フォールバックを設定する方法

@nuxtjs/fontaine は、フォールバック フォント指標値を自動的に計算し、フォールバック @font-face CSS を生成する Nuxt.js フレームワークのモジュールです。

モジュール構成に @nuxtjs/fontaine を追加してモジュールを有効にします。

import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
})

Google Fonts を使用している場合、またはフォントの @font-face 宣言がない場合は、追加オプションとして宣言できます。

ほとんどの場合、モジュールは CSS から @font-face ルールを読み取り、フォント ファミリー、代替フォント ファミリー、表示タイプなどの詳細を自動的に推測できます。

フォントがモジュールで検出できない場所で定義されている場合は、次のコード スニペットに示すように、指標情報を渡すことができます。

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
  fontMetrics: {
  fonts: ['Inter', { family: 'Some Custom Font', src: '/path/to/custom/font.woff2' }],
},
})

このモジュールは、CSS を自動的にスキャンして @font-face 宣言を読み込み、代替 @font-face ルールを生成します。

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}
/* This will be generated. */
@font-face {
  font-family: 'Roboto override';
  src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'),
    local('Arial'), local('Noto Sans');
  ascent-override: 92.7734375%;
  descent-override: 24.4140625%;
  line-gap-override: 0%;
}

次の例に示すように、CSS で Roboto override を代替フォントとして使用できるようになりました。

:root {
  font-family: 'Roboto';
  /* This becomes */
  font-family: 'Roboto', 'Roboto override';
}

CSS を自分で生成する

スタンドアロン ライブラリを使用して、代替のフォントサイズ調整用の CSS を生成することもできます。

Fontaine ライブラリの使用

Nuxt または Next.js を使用していない場合は、Fontaine を使用できます。Fontaine は、@nuxtjs/fontaine を支える基盤となるライブラリです。プロジェクトでこのライブラリを使用すると、Vite または Webpack プラグインで代替フォントの CSS を自動的に挿入できます。

CSS ファイルで Roboto フォントが定義されているとします。

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}

Fontaine には、ViteWebpack トランスフォーマーが用意されており、ビルドチェーンに簡単にプラグインできます。次の JavaScript に示すように、プラグインを有効にします。

import { FontaineTransform } from 'fontaine'

const options = {
  fallbacks: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
  // You may need to resolve assets like `/fonts/Roboto.woff2` to a particular directory
  resolvePath: (id) => 'file:///path/to/public/dir' + id,
  // overrideName: (originalName) => `${name} override`
  // sourcemap: false
}

Vite を使用している場合は、次のようにプラグインを追加します。 javascript // Vite export default { plugins: [FontaineTransform.vite(options)] }

Webpack を使用する場合は、次のように有効にします。

// Webpack
export default {
  plugins: [FontaineTransform.webpack(options)]
}

モジュールはファイルを自動的にスキャンして @font-face ルールを変更します。 css @font-face { font-family: 'Roboto'; font-display: swap; src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff'); font-weight: 700; } /* This will be generated. */ @font-face { font-family: 'Roboto override'; src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'), local('Arial'), local('Noto Sans'); ascent-override: 92.7734375%; descent-override: 24.4140625%; line-gap-override: 0%; }

CSS でフォールバック フォントとして Roboto override を使用できるようになりました。 css :root { font-family: 'Roboto'; /* This becomes */ font-family: 'Roboto', 'Roboto override'; }

Capsize ライブラリの使用

Next.js、Nuxt、Webpack、Vite を使用していない場合は、Capsize ライブラリを使用して代替 CSS を生成することもできます。

新しい createFontStack API

この API は、createFontStack という @capsize/core パッケージの一部であり、フォントスタック(font-family プロパティ)を指定するときと同じ順序でフォント メトリックの配列を受け入れます。

Capsize の使用方法については、こちらのドキュメントをご覧ください。

次の例について考えてみましょう。目的のウェブフォントは Lobster で、Helvetica Neue にフォールバックし、次に Arial にフォールバックします。CSS では font-family: Lobster, 'Helvetica Neue', Arial です。

  1. コアパッケージから createFontStack をインポートします。

    import { createFontStack } from '@capsizecss/core';
    
  2. 必要なフォントのフォント メトリックをインポートします(上記のフォント メトリックをご覧ください)。 javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`

  3. フォント スタックを作成し、font-family CSS プロパティを使用する場合と同じ順序で指標を配列として渡します。 javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);

これにより、次の結果が返されます。

{
  fontFamily: Lobster, 'Lobster Fallback: Helvetica Neue', 'Lobster Fallback: Arial',
  fontFaces: [
    {
      '@font-face' {
      'font-family': '"Lobster Fallback: Helvetica Neue"';
      src: local('Helvetica Neue');
      'ascent-override': '115.1741%';
      'descent-override': '28.7935%';
      'size-adjust': '86.8251%';
      }
     '@font-face' {
       'font-family': '"Lobster Fallback: Arial"';
       src: local('Arial');
       'ascent-override': 113.5679%;
       'descent-override': 28.392%;
       'size-adjust': 88.053%;
     }
   }
 ]
}

fontFamily コードと fontFaces コードを CSS に追加する必要があります。次のコードは、CSS スタイルシートまたは <style> ブロック内に実装する方法を示しています。

<style type="text/css">
  .heading {
    font-family: 
  }

  
</style>

生成される CSS は次のようになります。

.heading {
  font-family: Lobster, 'Lobster Fallback: Helvetica Neue',
    'Lobster Fallback: Arial';
}

@font-face {
  font-family: 'Lobster Fallback: Helvetica Neue';
  src: local('Helvetica Neue');
  ascent-override: 115.1741%;
  descent-override: 28.7935%;
  size-adjust: 86.8251%;
}
@font-face {
  font-family: 'Lobster Fallback: Arial';
  src: local('Arial');
  ascent-override: 113.5679%;
  descent-override: 28.392%;
  size-adjust: 88.053%;
}

@capsize/metrics パッケージを使用してオーバーライド値を計算し、CSS に適用することもできます。

const fontMetrics = require(`@capsizecss/metrics/inter`);
const fallbackFontMetrics = require(`@capsizecss/metrics/arial`);
const mainFontAvgWidth = fontMetrics.xAvgWidth / fontMetrics.unitsPerEm;
const fallbackFontAvgWidth = fallbackFontMetrics.xAvgWidth / fallbackFontMetrics.unitsPerEm;
let sizeAdjust = mainFontAvgWidth / fallbackFontAvgWidth;
let ascent = fontMetrics.ascent / (unitsPerEm * fontMetrics.sizeAdjust));
let descent = fontMetrics.descent / (unitsPerEm * fontMetrics.sizeAdjust));
let lineGap = fontMetrics.lineGap / (unitsPerEm * fontMetrics.sizeAdjust));