フォント読み込みとFOIT/FOUT(font-displayの内部)
なぜ文字が一瞬消えたり後から差し替わるのかを内部から説明でき、CLSを出さずにWebフォントを使い分けられるようになる。font-displayの3期間と状態機械、size-adjustによるフォールバック整合を原理から解説します。
- 1.font-displayはブロック期間・スワップ期間・フォールバック期間の3区間で状態が遷移する。ブロック中はテキストが不可視(FOIT)、スワップ中はフォールバック表示(FOUT)になり、フォールバック期間を過ぎるとWebフォントが届いても差し替えない。
- 2.auto/block/swap/fallback/optionalは同じ状態機械の3期間の長さを変えた設定にすぎない。blockはブロック約3秒、swapはブロックほぼ0秒、fallbackは短いブロック+短いスワップ、optionalはブロック約100ミリ秒でスワップ無しと整理できる。
- 3.差し替え時のCLSは、フォールバックとWebフォントのメトリクス差で行が伸縮するために起きる。size-adjust・ascent-override・descent-override・line-gap-overrideでフォールバックの寸法をWebフォントへ合わせれば、スワップしてもレイアウトが動かない。
なぜ文字が消えたり後から差し替わるのか
Webフォント(@font-face で url() 指定したフォント)は、HTMLパースとは独立にネットワークから取得されます。問題は、テキストを描画しようとした瞬間にそのフォントがまだ届いていないことが普通に起きる点です。このときブラウザが取る選択は2つしかありません。フォントが来るまでテキストを描かないか、手元にある別フォント(フォールバック)でひとまず描いておくかです。前者が起きると文字が一瞬消える FOIT(Flash of Invisible Text)、後者だと後で本フォントに差し替わる FOUT(Flash of Unstyled Text)になります。
font-display 記述子は、この「どこまで待ち、いつ諦め、いつ差し替えるか」を制御する仕組みです。重要なのは、これが1つの状態機械を3つの時間区間で駆動しているだけで、auto から optional までの5値は区間の長さを変えたプリセットにすぎない、という構造です。フォント取得がレンダリングを遅らせる全体像は クリティカルレンダリングパスの最適化原理 も合わせて押さえてください。
3つの期間とフォントの可視状態
ブラウザはテキストの描画が要求された時点から、対象フォントについてブロック期間 → スワップ期間 → フォールバック期間という3区間を時間順に進みます。各区間でフォントが「不可視」「フォールバックで可視」「本フォントで可視」のどの状態を取るかが決まっています。
| 期間 | フォント未着のときの表示 | 未着のまま期間を抜けると | 別名 |
|---|---|---|---|
| ブロック期間 | テキストを描かない(不可視) | スワップ期間へ移行 | FOITの原因 |
| スワップ期間 | フォールバックで描く | フォールバック期間へ移行 | FOUTの原因 |
| フォールバック期間 | フォールバックで描く | 以後Webフォントを使わない | 差し替え断念 |
ポイントは2つです。第一に、ブロック期間中にフォントが届けば FOIT も FOUT も起きません。最初から本フォントで描けるため、これが理想です。第二に、フォールバック期間を過ぎてから届いたフォントは、もうそのテキストには適用されません(次に同じフォントを使う要素には使われます)。これは「いつまでも遅れて差し替わってチラつく」のを防ぐための打ち切りです。
描画要求 ──[ブロック期間]──[スワップ期間]──[フォールバック期間]──→ 以後フォールバック固定
不可視で待つ フォールバック表示 フォールバック表示
(ここで届けば (届けば即差し替え) (届いても差し替えない)
FOITなし)
font-displayの5値は期間長のプリセット
auto / block / swap / fallback / optional の違いは、上の3期間の長さの割り当てだけで説明できます。具体的な秒数は仕様では「短い(おおむね100ミリ秒程度)」「長くても3秒程度」と幅をもって定義され、実装で多少前後しますが、各値の設計意図は次の通り明確です。
| 値 | ブロック期間 | スワップ期間 | 結果として起きやすい現象 |
|---|---|---|---|
| block | 長い(約3秒) | 無限 | FOIT。遅いと長く文字が消える |
| swap | ほぼ0秒 | 無限 | ほぼ確実にFOUT。最速で文字は出る |
| fallback | 短い(約100ミリ秒) | 短い(約3秒) | 短いFOIT後にFOUT。遅ければ断念 |
| optional | 短い(約100ミリ秒) | 0秒 | ほぼフォールバック固定。差し替えしない |
| auto | ブラウザ任せ | ブラウザ任せ | 多くの実装でblock相当(FOIT寄り) |
- block:ブロック期間が長く、その後のスワップ期間は無限。最後まで本フォントを待つので見た目は崩れませんが、遅いと文字が長く消えます。ロゴやアイコンフォントなど「別フォントで出ると無意味」な用途向きです。
- swap:ブロック期間がほぼ0なので即フォールバック表示し、届いたら差し替えます。本文を速く読ませたいときの定番ですが、差し替え時にレイアウトが動けば CLS を出します。
- fallback:短いブロックで軽い FOIT を許し、その後は短いスワップ。遅延が大きいと差し替えを断念するため、チラつきの最大量を抑えられます。
- optional:スワップ期間が実質0。短いブロック中に間に合わなければそのページではフォールバックに固定し、本フォントは次回(キャッシュ済み)に回します。差し替え由来の CLS を構造的に出さない選択です。
optional は「初回訪問で間に合わないなら、今回はフォールバックで通し、取得したフォントはキャッシュに入れて次回から使う」という割り切りです。差し替えそのものを起こさないため、レイアウトシフトを根本から封じられます。ブランドフォントを使いつつ CLS を絶対に出したくない本文に向きます。
CSS Font Loading APIとの関係
font-display は宣言的な制御ですが、同じ状態をスクリプトから観測・制御できるのが CSS Font Loading API です。document.fonts(FontFaceSet)は各フォントの読み込み状態を持ち、document.fonts.ready で全フォント確定を待てます。
// 本フォント確定後にだけ本文を表示する、といった明示制御ができる
document.fonts.ready.then(() => {
document.documentElement.classList.add("fonts-loaded");
});
// 個別フォントを先に読み込み、完了後に自前でクラスを付け替える
const f = new FontFace("Inter", "url(/fonts/inter.woff2)");
f.load().then((loaded) => document.fonts.add(loaded));
この API を使うと、「フォント未確定の間は意図したフォールバックで描き、確定したらクラス切り替えで一括スワップ」という FOUT を制御下に置く手法が組めます。font-display が時間で自動遷移するのに対し、API は遷移の契機をアプリ側が握れるのが違いです。
差し替えで起きるCLSとsize-adjust整合
FOUT のスワップで体感品質を最も損なうのがレイアウトシフトです。フォールバックフォントと本フォントは、同じ font-size でも1文字の幅(アドバンス幅)や行の高さ(アセント/ディセント)が異なります。差し替えた瞬間に文字幅と行の高さが変わり、後続の行が上下にずれて CLS が加算されます。CLS の採点式そのものは Core Web Vitalsの計測アルゴリズム を参照してください。
根本対策は、フォールバックの寸法を本フォントへ合わせ込むことです。@font-face でフォールバック専用の別フェイスを定義し、次の4つの記述子でメトリクスを上書きします。
| 記述子 | 上書きするもの | 効果 |
|---|---|---|
| size-adjust | グリフ全体の拡大率(%) | 字幅・字高をまとめて本フォントに近づける |
| ascent-override | ベースラインより上の高さ | 行の上側の占有量を一致させる |
| descent-override | ベースラインより下の高さ | 行の下側の占有量を一致させる |
| line-gap-override | 行間に足す追加スペース | 行の総高さ(行送り)を一致させる |
これらでフォールバックの行ボックス寸法を本フォントと一致させておけば、スワップが起きても1行の高さと折り返し位置が変わらず、レイアウトが動きません。つまり「FOUT は許すが CLS は出さない」状態を作れます。
/* 本フォント */
@font-face {
font-family: "Inter";
src: url("/fonts/inter.woff2") format("woff2");
font-display: swap;
}
/* 寸法を Inter に合わせたフォールバック専用フェイス */
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%; /* Arial を Inter の字幅へ補正 */
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
/* 本フォント未着の間は寸法整合済みフォールバックで描く */
font-family: "Inter", "Inter Fallback", sans-serif;
}
記述子の数値は、本フォントとフォールバックのフォントメトリクス(unitsPerEm に対する ascent / descent / advance の比)から算出します。Next.js の next/font などは本フォントのメトリクスを読み取り、これら4記述子を自動生成して整合フェイスを差し込みます。手動なら、本フォントの advance 平均をフォールバックの advance 平均で割った値が size-adjust の出発点になります。
取得そのものを速くする
期間制御や寸法整合は「間に合わなかったとき」の被害を抑える話です。そもそもブロック期間中に届けば FOIT も FOUT も起きないため、取得の前倒しが最上策になります。
- preload:
<link rel="preload" as="font" type="font/woff2" crossorigin>で、CSS解析を待たずに最優先取得を開始させます。@font-faceだけだと、CSSOM構築後にフォント取得が始まるため遅れます。リソース優先度の効かせ方は クリティカルレンダリングパスの最適化原理 を参照してください。 - crossorigin 必須:フォントは匿名 CORS で取得されるため、
preloadにもcrossoriginを付けないと preload が別リクエスト扱いになり二重取得になります。 - WOFF2 とサブセット:圧縮率の高い WOFF2 を使い、使う字種だけにサブセット化すれば転送量が減り、ブロック期間内に収まりやすくなります。
- unicode-range 分割:字種ごとにファイルを分け、
unicode-rangeで必要な範囲だけを取得させると、本文に使う範囲を最優先で届けられます。
preload は最優先キューに入るため、本当に初回表示に必要なフォント(本文1ウェイト程度)だけに絞ります。装飾用や全ウェイトを preload すると、画像など他のクリティカルリソースの帯域を奪い、かえって LCP を悪化させます。
まとめ
font-display はブロック期間・スワップ期間・フォールバック期間の3区間からなる単一の状態機械で、ブロック中は不可視(FOIT)、スワップ中はフォールバック表示(FOUT)、フォールバック期間を過ぎると差し替えを断念します。block / swap / fallback / optional / auto は各期間の長さを変えたプリセットで、swap は最速表示だが差し替えで CLS を生み、optional は差し替え自体を捨てて CLS を封じます。FOUT の CLS はフォールバックと本フォントのメトリクス差で起きるため、size-adjust / ascent-override / descent-override / line-gap-override でフォールバック寸法を本フォントへ整合させれば、スワップしてもレイアウトが動きません。最善は preload(crossorigin 付き)と WOFF2 サブセットでブロック期間中に取得を終えることです。行のずれが重い理由は リフローとリペイントを引き起こすCSSプロパティの分類、CLS の採点は Core Web Vitalsの計測アルゴリズム で補完してください。
Web/フロントエンド Article
フォント読み込みとFOIT/FOUT(font-displayの内部)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Webフォント
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 6
導入後に効く点
auto/block/swap/fallback/optionalは同じ状態機械の3期間の長さを変えた設定にすぎない。blockはブロック約3秒、swapはブロックほぼ0秒、fallbackは短いブロック+短いスワップ、optionalはブロック約100ミリ秒でスワップ無しと整理できる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Webフォント / font-display」に近いか確認する。
- 強みである「font-displayはブロック期間・スワップ期間・フォールバック期間の3区間で状態が遷移する。ブロック中はテキストが不可視(FOIT)、スワップ中はフォールバック表示(FOUT)になり、フォールバック期間を過ぎるとWebフォントが届いても差し替えない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。