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

Janicklas Ralph James
Janicklas Ralph James

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

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

この記事では、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 Fonts またはカスタム フォントをページにインポートするための 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 の場合: Cumulative Layout Shift を削減するために自動代替フォントを使用するかどうかを設定するブール値。デフォルトは true です。Next.js は、フォントタイプ(それぞれ Serif と Sans Serif)に応じて代替フォントを Arial または Times New Roman に自動的に設定します。

@next/font/local の場合: Cumulative Layout Shift を削減するために自動代替フォントを使用するかどうかを設定する文字列またはブール値の false。指定できる値は ArialTimes New Romanfalse です。デフォルトは Arial です。Serif フォントを使用する場合は、この値を Times New Roman に設定することを検討してください。

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

next/font コンポーネントを使用できない場合は、optimizeFonts フラグを使用する方法でも、Google Fonts でこの機能を使用できます。Next.js では、OptimizeFonts 機能がデフォルトで有効になっています。この機能は、HTML レスポンスに Google Font CSS をインライン化します。さらに、次のスニペットに示すように、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 は、ビルドチェーンに簡単にプラグインできる Vite トランスフォーマーと Webpack トランスフォーマーを提供しています。以下の 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));

謝辞

Alexander Andrews 氏による Unsplash のヒーロー画像