Webフォントの「読み込み戦略」を再定義する:font-displayが切り拓くUXの深淵
Webパフォーマンスを突き詰めていくと、避けて通れないのが「Webフォント」という名の魔物です。美しいタイポグラフィはブランドの魂ですが、それがレンダリングのブロッキング要因となり、ユーザー体験を損なうならば、それは技術的な負債以外の何物でもありません。
今回は、単なるCSSのプロパティ解説に留まらず、ブラウザのレンダリングパイプラインをハックする視点で、`font-display`を核とした堅牢なフォント読み込み戦略を深掘りします。
—
1. なぜ「FOIT/FOUT」はエンジニアを悩ませるのか
ブラウザはWebフォントをダウンロードする際、内部でフォントの「読み込み状態」を管理しています。ここで発生するのが以下の二大問題です。
- FOIT (Flash of Invisible Text): フォント読み込み完了までテキストを非表示にする。低速回線では致命的な「空白時間」を生む。
- FOUT (Flash of Unstyled Text): 代替フォント(システムフォント)を先に表示し、読み込み後にWebフォントへ差し替える。レイアウトシフト(CLS)を引き起こしやすい。
これらを制御するのが `font-display` です。しかし、このプロパティを単に `swap` にすれば良いという単純な話ではありません。アーキテクチャ全体で見た時、どのような影響があるのかを理解する必要があります。
—
2. 賢いエンジニアのための `font-display` 戦略
swap: 最も現実的な「妥協」
@font-face {
font-family: ‘Inter’;
src: url(‘/fonts/inter.woff2’) format(‘woff2’);
/ 即座にシステムフォントを表示し、読み込み完了後に差し替える /
font-display: swap;
}
`swap` は最も一般的ですが、フォント切り替え時の「ガクッ」というリフローを招きます。これを防ぐには、`font-size-adjust` プロパティを併用し、フォント間のxハイト(小文字の高さ)を揃える設計が不可欠です。
fallback: パフォーマンスの「守護神」
@font-face {
font-family: ‘Inter’;
font-display: fallback;
/
100ms程度の極めて短い期間だけ非表示。
その後、読み込みが遅ければ代替フォントで固定。
ページ遷移してもWebフォントは裏で読み込まれ、次回訪問から適用される。
/
}
Core Web Vitalsの観点では、極めて優秀です。レイアウトシフトを最小限に抑えつつ、ユーザー体験を損なわない。コンバージョンを重視するLPや、テキスト主体のアプリケーションには最適解となり得ます。
optional: 究極の「安定」
@font-face {
font-family: ‘Inter’;
font-display: optional;
/
ブラウザが「通信環境が良好」と判断した場合のみ使用。
低速時は最初から代替フォントのみ。
一度読み込めばキャッシュされるため、二回目以降のUXが極めて安定する。
/
}
「フォントが切り替わる一瞬のチラつき」すら許容できない、堅牢なB2Bダッシュボードなどでは、こちらを推奨します。
—
3. TypeScriptとCSS設計:型安全なフォント管理
大規模なアプリケーションでは、フォントのロード状態をJS側でハンドリングする必要が出てきます。`FontFaceSet` APIを活用し、フォントのロード完了をフックしてアニメーションを制御する設計がスマートです。
// フォントの読み込みを厳格に管理するためのラッパー
type FontStatus = ‘loading’ | ‘loaded’ | ‘error’;
class FontLoader {
static async load(fontFamily: string): Promise
try {
// document.fonts API を使用したモダンな非同期制御
await document.fonts.load(`1em ${fontFamily}`);
return ‘loaded’;
} catch (e) {
console.error(`Font loading failed: ${fontFamily}`, e);
return ‘error’;
}
}
}
// React等のコンポーネント内での活用例
const useFontReady = (fontName: string) => {
const [ready, setReady] = React.useState(false);
React.useEffect(() => {
FontLoader.load(fontName).then(() => setReady(true));
}, [fontName]);
return ready;
};
—
4. 現場のリアル:エッジケースと最適化の泥臭い知見
最後に、教科書には載っていない「現場の罠」を共有します。
1. メモリ効率への意識:
すべてのウェイト(Thin, Regular, Bold, Black)をプリロードしてはいけません。フォントファイルはバイナリデータであり、ブラウザのメモリを確実に食います。`unicode-range` を活用し、必要な文字セット(例えば日本語ならひらがな+カタカナ+常用漢字の一部)だけを分割ロードさせる設計が、真のパフォーマンスチューニングです。
2. リフロー・リペイントの連鎖:
フォントが差し替わった瞬間、`width` や `height` が計算し直される「リフロー」が発生します。これを防ぐために、CSSで `letter-spacing` や `line-height` を微調整し、代替フォントとWebフォントの表示面積を極限まで近づける「フォントの正規化」を行ってください。
3. 非同期競合の回避:
`font-display` を適切に設定しても、サードパーティのスクリプトがヘッドで同期的にロードされていると、ブラウザのメインスレッドは奪い合いになります。`` でフォントを優先順位のトップに据えつつ、HTTP/2の並列ロードを活かすのが鉄則です。
結論:技術は「消し去る」ためにある
究極のフロントエンド開発とは、ユーザーがWebフォントの存在すら意識せず、ただ「読みやすい」と感じる環境を作ることです。`font-display` は、そのための強力なハンドルのひとつに過ぎません。
あなたが書くそのCSSの一行が、誰かの数ミリ秒を節約し、ひいてはプロダクトの信頼性を支える。そう信じて、今日も最適化を続けていきましょう。コードは嘘をつきませんから。

コメント