FlowingDev

@font-face 完全解説: ブラウザにあなたのタイポグラフィを語らせよう

font-display、プリロード、unicode-range、バリアブルフォントを駆使して、カスタムWebフォントを効率的に配信するためのCSS @font-face ルールをマスターしよう。

ツールを試す: @font-face ジェネレーター

一言でいうと

@font-face は、WebブラウザにカスタムタイポグラフィをダウンロードさせてレンダリングするためのCSSルールだ。これにより、すべてのPCにプリインストールされている数種類の「Webセーフ」フォントの専制政治から、デザイナーたちをついに解放した。

解決する課題

90年代後半から2000年代前半にWebサイトを作っていたなら知っていると思うが、当時のWebタイポグラフィは荒涼とした不毛の地だった。ユーザーのOSにたまたま入っているフォントに運命を委ねるしかなかったのだ。font-family: Arial, Helvetica, sans-serif; と書いて、あとは祈るだけ。もしデザイナーが、見出しに美しい特注のセリフフォントを使ったPhotoshopのモックアップを渡してきたら、開発者には3つの地獄のような選択肢しかなかった。

  1. 画像ハック(The Image Hack): Photoshopで見出しをスライスして <img> タグとして配信する。SEO的には最悪。スクリーンリーダーも読めない。のちのRetinaディスプレイ時代にはただのボヤけた塊になった。
  2. sIFR (Scalable Inman Flash Replacement): JavaScriptでテキストを見つけて隠し、カスタムフォントで全く同じテキストをレンダリングする極小のAdobe Flashムービーを埋め込むという悪魔のようなワークアラウンド。重くて、バグだらけで、ノートPCのバッテリーを溶かした。
  3. Cufón: フォントのパスをベクターグラフィックスに変換して <canvas> にレンダリングするJavaScriptライブラリ。見た目は美しいが、テキストを簡単に選択したりコピーしたりできなかった。

実はW3CはCSS2(1998年)の時点で @font-face を提案していたのだが、ブラウザ間でフォントフォーマットの合意が取れず、さらにフォントメーカーは海賊版を恐れていた(サーバーに .ttf ファイルを置けば誰でもダウンロードできてしまうため)。

@font-face が現実的なものになったのは、Web向けに特別に圧縮・最適化されたフォーマットであるWeb Open Font Format(WOFF)が広く普及した2010年代に入ってからのことだ。今や @font-face は単にフォントを読み込むだけではない。ネットワークリクエスト、キャッシング、サブセット化、そしてレンダリング戦略といった複雑なダンスを指揮し、サイトの爆速を保ちつつ、デザイナーの意図通りの見た目を実現しているのだ。

裏側の仕組み

核心を言えば、@font-face は一連の命令セットだ。自分が勝手に決めた名前を、サーバー上に置かれているファイルにマッピングする。

ルールの解剖学

モダンで本番環境に耐えうる @font-face の宣言は、だいたいこんな感じだ。

@font-face {
  font-family: 'MyAwesomeFont';
  src: local('MyAwesomeFont Regular'), 
       url('/fonts/myawesomefont.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF;
}

このブロックの中で起きている魔法を解き明かしていこう。

フォーマットとWOFF2の独占状態

format('woff2') に注目してほしい。暗黒時代、開発者は .eot(古代のInternet Explorer用)、.ttf(古いSafari/Android用)、.svg(レガシーiOS用)、そして .woff という、巨大で「弾を通さない(bulletproof)」フォントスタックを用意しなければならなかった。

今日のWebは WOFF2 で動いている。Brotli圧縮を採用しており、前身のWOFFよりもファイルサイズが約30%小さい。WOFF2はほぼ普遍的なブラウザサポート(世界で97%以上)を誇るため、非常に古いエンタープライズ向けPCを相手にする場合を除き、基本的には .woff2 ファイルを1つ、念のため標準のWOFFをフォールバックとして提供するだけで十分だ。

フラッシュの制御(font-display)

ブラウザがカスタムフォントでスタイル付けされたテキストを見つけたものの、フォントファイルのダウンロードがまだ終わっていない時、ブラウザは選択を迫られる。フォールバック(代替)フォントでテキストを表示するか?それともカスタムフォントが届くまでテキストを隠すか?

ここで悪名高い2つの頭字語が生まれる。FOUT (Flash of Unstyled Text: スタイルが当たっていないテキストのちらつき) と FOIT (Flash of Invisible Text: 見えないテキストのちらつき) だ。font-display ディスクリプタを使えば、この挙動をコントロールできる。

font-display 挙動 いつ使うべきか
auto ブラウザのデフォルト(通常、最大3秒間レンダリングをブロックする)。 パフォーマンスを気にするなら絶対に使わない。
swap すぐにフォールバックフォントを表示し(FOUT)、準備ができたらカスタムフォントに入れ替える。 本文、記事、UI要素。ユーザーはすぐに読み始められる。
block 短時間テキストを隠し(FOIT)、その後フォールバックを表示し、さらに入れ替える。 フォールバックフォントが文字化けのように見えてしまうアイコンやロゴ。
fallback 短いブロック期間と、短い入れ替え期間。時間がかかりすぎた場合は、そのページロードでのカスタムフォントの適用を完全に中止する。 遅い回線環境下での装飾的なテキスト。
optional 極めて短いブロック期間。フォントがキャッシュされていないか、通信が爆速でない限り、完全に諦める。 レイアウトの安定性が最優先される場合の「あったら嬉しい」レベルのフォント。

unicode-rangeによるサブセット化

ラテン語、キリル文字、ギリシャ文字、そして複雑なCJK(中国語、日本語、韓国語)のグリフを含む完全なフォントファイルは、平気で数メガバイトの重さになる。

unicode-range プロパティは、素晴らしい最適化ツールだ。「このフォントファイルは、これらの特定の文字だけをカバーしているよ」とブラウザに伝えることができる。 もし unicode-range: U+0000-00FF(基本ラテン文字)と設定すると、ブラウザはHTMLをパースし、ラテン文字が1つも見つからなければ、フォントファイルをダウンロードすることすらしない。これにより、巨大なフォントを小さな言語チャンク(塊)に分割し、ユーザーが実際に必要とするものだけをロードさせることが可能になる。

バリアブルフォント(Variable Fonts)

従来は、Regular、Italic、Bold、Bold Italicを使いたい場合、4つの個別の .woff2 ファイルを読み込む必要があった。

バリアブルフォントは、「バリエーションの軸(axes of variation)」を定義することで、フォントファミリー全体を1つのファイルにバンドルする。400 や 700 のような静的なウェイトの代わりに、バリアブルフォントは 432 や 781 といったウェイトでレンダリングできるのだ。

@font-face {
  font-family: 'MyVariableFont';
  src: url('/fonts/myfont-var.woff2') format('woff2');
  font-weight: 100 900; /* 100から900の間の任意のウェイトを受け付ける */
}

Base64埋め込み vs プリロード

時にはネットワークリクエスト自体がボトルネックになることもある。その場合、2つの高度な戦略がある。

  1. プリロード: HTMLの <head> に <link rel="preload" href="/fonts/myfont.woff2" as="font" type="font/woff2" crossorigin> を追加することで、CSSをダウンロードするよりも先に、ブラウザに対して直ちにフォントを取得するように指示できる。
  2. Base64埋め込み: バイナリのフォントファイルを巨大なテキスト文字列に変換し、@font-face ルールの url() に直接貼り付けることができる。これによりネットワークリクエストは完全に排除されるが、CSSのファイルサイズは約33%増加する。CSSがダウンロードされるまでページ全体のレンダリングがブロックされるため、この手法は極小で極限まで最適化されたサブセットフォント(カスタムロゴなど)にのみ厳格に限定して使うべきだ。

現実世界でのストーリー

消えたECサイトのボタン

あるブティックのECサイトが、UIにカスタムアイコンフォントを使うことにした。彼らは font-display を付けずに標準の @font-face 宣言を使用した。オフィスの高速Wi-Fi環境では、すべてが素晴らしく見えた。

しかし、3G回線を使うモバイルユーザーの直帰率が記録的な数字になっていた。開発者が遅いネットワークをシミュレートしたところ、犯人が判明した。ブラウザが「カートに追加」ボタンをレンダリングする前に、アイコンフォントの読み込みを待っていたのだ。結果として4秒間のFOIT(見えないテキストのちらつき)が発生。ユーザーは会社にお金を払いたくても、物理的にボタンが見えなかったのだ。テキスト用に font-display: swap; を追加し、アイコンフォントをプリロードするだけで、彼らのコンバージョン率は救われた。

数メガバイトのタイポグラフィの悲劇

人気のテックブログが国際化対応を行うことにした。洗練されたタイポグラフィの一貫性を保つため、CJK(中国語、日本語、韓国語)文字をフルでカバーする巨大なNoto Sansフォントを導入した。フォントファイルはなんと約6MBもあった。

突然、アメリカで普通の英語記事を読んでいるユーザーから、ロードが遅いという苦情が殺到し始めた。彼らのブラウザは、JavaScriptに関する記事を読むためだけに6MBもの日本語や韓国語のグリフをダウンロードしていたのだ。チームは unicode-range を実装した。フォントを30KBのラテン文字サブセットと、5.9MBのCJKサブセットに分割したのだ。ブラウザは英語の読者には30KBのファイルだけを自動的にリクエストするようになり、パフォーマンスの低下は即座に解消された。

ガタガタなリデザイン

あるデザインエージェンシーが、クラシックなサンセリフフォントの9種類のウェイトを駆使して、見事なエディトリアルレイアウトを作り上げた。開発者は律儀に9種類の .woff2 ファイルを書き出し、9つの @font-face ルールを書いた。

ブラウザはファイルを非同期でダウンロードするため、ページはガタガタと引っかかりながら表示された。300 のウェイトが読み込まれ、次に 700、そして 400 が読み込まれ、その度にレイアウトがアグレッシブにスナップし、1秒間に4〜5回もテキストの折り返しを再計算した。開発者は9つのファイルを1つのバリアブルフォント(.woff2-variations)に置き換えた。その1つのファイルは9つを合わせたものより40%小さく、1回のリクエストで読み込まれ、ページは一発でスムーズにレンダリングされるようになった。

よくある間違いと罠

  • 古代のフォントフォーマットをモダンブラウザに配信する: IE8や10年前のAndroidデバイスをサポートしなければならないという明確でデータに基づく理由がない限り、.ttf、.eot、.svg などのフォントを配信するのはやめよう。WOFF2一択でいくべきだ。
  • プリロードでの crossorigin の付け忘れ: フォントに対して <link rel="preload"> を使用する場合、たとえ自分のドメインでホストされているフォントであっても、必ず crossorigin 属性を含めなければならない。ブラウザのセキュリティ仕様上、フォントはアノニマスモードのCORSを使用して取得される。この属性を省略すると、ブラウザはフォントを2回ダウンロードしてしまう。
  • 巨大な本文用フォントのBase64エンコーディング: 100KBのフォントを直接CSSに埋め込むと、CSSのペイロードが肥大化し、レンダリングがブロックされる。小さなフォント(ロゴに使われる5KBのサブセットなど)のみをエンコードすること。
  • font-displayの無視: ブラウザのデフォルトの挙動に依存すると、ほぼ確実に遅い回線のユーザーを苦しめることになる。本文には明示的に font-display: swap を宣言し、見た目よりも読むことを優先しよう。
  • ページ上の全フォントのプリロード: プリロードは優先度の高い命令だ。ページのずっと下の方にある太字斜体の見出しフォントをプリロードしてしまうと、ページ上部の読み込みに必要なクリティカルなJavaScriptやCSSからネットワーク帯域幅を奪うことになる。プリロードするのは「ヒーロー」フォント(最上部で目立つフォント)だけにしよう。
  • local() への過度な依存: local('Helvetica') を使うと、ローカルにコピーがあればそれを使うようブラウザに指示できる。しかし、フォントの命名規則はMac、Windows、Linuxの間で激しく異なる。あなたが local('Helvetica Neue Bold') だと思っているものは、別のOSでは何のエラーも出さずにサイレントに失敗するかもしれない。

なぜ知っておくべきなのか

Web開発とは、美学とパフォーマンスの間の終わりのない綱引きだ。あなたはブランドを構築しており、タイポグラフィはそのブランドの声である。しかし同時にあなたはソフトウェアも構築しており、地下鉄の不安定な回線にいるユーザーにとっては1キロバイトが命取りになる。

完璧な @font-face ルールを書くのは、信じられないほど面倒な作業だ。ブラウザの癖、MIMEタイプ、サブセットの構文、圧縮フォーマットについての深い理解が必要になる。モダンなジェネレーターは、単にCSSの構文を書いてくれるだけでなく、ベストプラクティスをエンコードしてくれる。自動的にWOFF2ファイルを生成し、バリアブルフォントの軸を構成し、正しい font-display のフォールバックを適用し、ネットワークのボトルネックを回避するために必要な正確なBase64やプリロードタグを出力してくれる。あなたのアプリケーションが言葉を発する時、ブラウザがその単語をどう発音すべきかを正確に理解できるようにしてくれるのだ。

さらに深掘りする

理論はOK。さあ手を動かそう — 100%ブラウザ内で。

ツールを試す: @font-face ジェネレーター