1. ホーム
  2. CSS

【CSS】@font-face の使い方|Webフォントを読み込んで独自のフォントを使う方法

Share

サイトのデザインに合ったフォントを使いたいのに、そのフォントが閲覧者のパソコンやスマートフォンに入っているとは限りません。@font-face を使うと、サーバー上に置いたフォントファイルをブラウザーに読み込ませ、好きな名前を付けて font-family から呼び出せます。この記事では、@font-face の基本の書き方から、src / font-weight / font-display / unicode-range といった記述子(ディスクリプタ)の役割、そしてフォントが表示されないときの原因の切り分け方までを解説します。

@font-face はフォントファイルに名前を付けるルール

@font-face は「どこにあるフォントファイルを、CSS の中で何という名前で呼ぶか」を定義するアットルールです。ここで登録した名前を font-family に書くと、そのフォントが使われます。定義そのものは見た目を変えず、使う側の font-family とセットで初めて効くのがポイントです。

style.css
/* 1. フォントファイルに「MyFont」という名前を付ける */
@font-face {
  font-family: "MyFont";
  src: url("../fonts/myfont.woff2") format("woff2");
}

/* 2. 付けた名前を font-family で呼び出す */
body {
  font-family: "MyFont", sans-serif;
}

名前は既存のフォント名と同じである必要はなく、"MyFont" のように自由に決められます。ただし sans-serifmonospace といった総称ファミリー名は使えません。また @font-face はスタイルシートのトップレベル(またはメディアクエリなどの条件付きグループ規則の中)に書くルールで、.header { ... } のようなセレクターの中には書けません。

font-family の指定で "MyFont" のうしろに sans-serif を並べているのも大事な点です。フォントの読み込みに失敗したときや、そのフォントに含まれていない文字(欧文フォントで日本語を表示する場合など)は、うしろに書いたフォントが使われます。Webフォントを指定するときも、フォールバックのフォントは必ず書いておきます。

@font-face で使える主な記述子

@font-face の中に書くものは、通常のスタイル宣言ではなく「記述子(ディスクリプタ)」と呼ばれます。同じ名前でも font-weight: bold; のような普通のプロパティとは意味が違い、「この face はどういう条件のときに使われるか」を宣言するものです。よく使うものは次のとおりです。

記述子役割
font-familyこのフォントを呼び出すための名前(必須)
srcフォントファイルの場所と形式。複数書ける(必須)
font-weightこの face が担当する太さ。可変フォントは 400 700 のようにレンジで書ける
font-styleこの face が担当する字形(normal / italic / oblique
font-display読み込み中とその後の表示方法(auto / block / swap / fallback / optional
unicode-rangeこの face を使う文字の範囲。既定値は U+0-10FFFF(すべて)
font-stretchこの face が担当する字幅(condensed など)
size-adjust字面の大きさを百分率で調整する。フォールバックとの差を詰めるときに使う
font-feature-settingsOpenType 機能の既定値(合字や旧字体の切り替えなど)

必須なのは font-familysrc の2つだけです。ほかを省略した場合は既定値(font-weight: normalfont-style: normal、すべての文字を担当)として登録されます。以降でよく使うものを個別に見ていきます。

src で読み込み元と形式を指定する

src にはフォントの入手先をカンマ区切りで並べます。書けるのは url()(ファイルの場所)と local()(端末にインストール済みのフォント名)の2種類です。ブラウザーは先頭から順に見て、最初に使えたものを採用し、そこで探索をやめます。

style.css
@font-face {
  font-family: "MyFont";
  /* 端末にあればそれを使い、なければ woff2、それも駄目なら woff */
  src: local("MyFont"),
       url("../fonts/myfont.woff2") format("woff2"),
       url("../fonts/myfont.woff") format("woff");
}

format() は、そのファイルがどの形式かをブラウザーに伝えるヒントです。書いておくと、対応していない形式のファイルをダウンロードする前に読み飛ばせます。指定できるキーワードは次のとおりで、拡張子ではなく形式名で書く点に注意してください。

format() の値対応するファイル
format("woff2")WOFF 2.0(.woff2
format("woff")WOFF 1.0(.woff
format("truetype")TrueType(.ttf
format("opentype")OpenType(.otf / .ttf
format("collection")フォントコレクション(.ttc / .otc
format("embedded-opentype")EOT(.eot。古い Internet Explorer 向け)

いまは woff2 だけで足りることが多い

WOFF 2.0 は Web 用にフォントを圧縮する形式で、Brotli 圧縮を採用しているぶん、同じフォントでも WOFF 1.0 より小さくなります。.ttf.otf をそのまま置くのに比べると差はさらに大きく、日本語フォントのように元のファイルが重いものほど効果があります。

対応状況も、Chrome・Edge・Firefox・Safari の現行版はいずれも WOFF 2.0 を読めます。.eot.svg を並べる書き方は Internet Explorer や古いモバイルブラウザーへの対策として広まったもので、いま新しく書くなら woff2 だけ、必要に応じて woff を1行足す程度で十分です。並べるファイルが減れば、その分だけ記述もサーバー上のファイルも整理できます。

local() で端末のフォントを使う

local() はダウンロードではなく、端末にインストール済みのフォントを名前で探す指定です。見つかればネットワーク通信が発生しないので表示が速くなります。ただし、フォント名は OS や言語環境によって表記が違い、意図しない別のフォントが当たることもあるため、確実に同じ見た目にしたいときは url() だけにするほうが安全です。

下のデモは、この local() だけで @font-face を組み立てたものです。"DemoFont" という名前を新しく作り、通常の字形と斜体の字形をそれぞれ別の @font-face として登録しています。CSS タブの local("Georgia")local("Courier New") などに書き換えると、呼び出し側の .sample はそのままで表示だけが変わることを確認できます。1行目の和文が欧文と違う書体で表示されるのは、指定したフォントに含まれない文字が font-family の次の指定に回っているためです。

<p class="sample">The quick brown fox / 和文は次のフォントに回ります</p>
<p class="sample sample--italic">The quick brown fox(font-style: italic;)</p>
<p class="default">The quick brown fox / 比較用:既定のフォント</p>
/* src に local() だけを書くと、端末にインストール済みのフォントを名前で探して使う。
   ファイルをダウンロードしないので、ここで @font-face の動きだけを確認できる */
@font-face {
  font-family: "DemoFont"; /* この名前で font-family から呼び出せるようになる */
  src: local("Georgia"), local("Times New Roman");
  font-style: normal;
  font-weight: 400;
}

/* 斜体は「別のフォントファイル」なので、同じ font-family 名で別に登録する */
@font-face {
  font-family: "DemoFont";
  src: local("Georgia Italic"), local("Times New Roman Italic");
  font-style: italic; /* font-style: italic; のときに選ばれる face */
  font-weight: 400;
}

body {
  margin: 12px;
  font-family: sans-serif;
}

/* 登録した名前を使う。見つからなかったときは後ろの serif が使われる */
.sample {
  font-family: "DemoFont", serif;
  font-size: 20px;
}

.sample--italic {
  font-style: italic;
}

.default {
  font-size: 20px;
  color: #6b7280;
}
Preview

太さや斜体は同じ名前で複数登録する

フォントは通常、レギュラー・ボールド・イタリックがそれぞれ別のファイルとして配布されます。これらは同じ font-family 名で、font-weightfont-style の値を変えて別々に登録します。こうしておくと、使う側は font-family を一度書くだけで、font-weight: 700; と書いたときに自動でボールドのファイルが使われます。

style.css
/* レギュラー */
@font-face {
  font-family: "MyFont";
  src: url("../fonts/myfont-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
}

/* ボールド(ファイルは別、名前は同じ) */
@font-face {
  font-family: "MyFont";
  src: url("../fonts/myfont-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
}

/* イタリック */
@font-face {
  font-family: "MyFont";
  src: url("../fonts/myfont-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
}

.lead {
  font-family: "MyFont", sans-serif;
  font-weight: 700; /* 上のボールドの face が選ばれる */
}

可変フォントは範囲で書く

1つのファイルに太さの連続的な変化を持つ可変フォント(バリアブルフォント)の場合は、font-weight下限と上限をスペース区切りで書きます。次の例なら、100 から 900 までの任意の値を指定できます。

style.css
@font-face {
  font-family: "MyVariable";
  src: url("../fonts/myvariable.woff2") format("woff2");
  font-weight: 100 900; /* この範囲の太さを1ファイルでまかなう */
  font-style: normal;
}

.heading {
  font-family: "MyVariable", sans-serif;
  font-weight: 620; /* 100 刻みでない値も指定できる */
}

レンジを書いておかないと、可変フォントであってもブラウザーは既定値の normal(400)だけを担当する face とみなします。その状態で font-weight: 700; を指定すると、可変フォント側の太いデザインが使われず、後述の合成太字になってしまうことがあります。

font-display で読み込み中の見え方を決める

Webフォントはダウンロードが終わるまで使えません。その間の表示をどうするかを指定するのが font-display です。ブラウザーは内部的に、フォント待ちの時間を「ブロック期間(テキストを描画せず待つ)」と「スワップ期間(代替フォントで表示し、届いたら差し替える)」に分けて扱います。font-display は、この2つの長さの組み合わせを選ぶ指定だと考えると分かりやすいです。

ふるまい
autoブラウザーに任せる(既定値)。多くは block に近い動き
block短い間テキストを描画せずに待ち、そのあとは届き次第いつでも差し替える
swapほぼ待たずに代替フォントで表示し、届き次第いつでも差し替える
fallbackほぼ待たずに代替フォントで表示。差し替えは短い期間だけ受け付け、それ以降に届いた分は次の表示から使う
optionalほぼ待たず、間に合わなければそのページでは使わない。回線が遅い環境では読み込み自体を諦めることもある

ブロック期間中はテキストが見えないため、この状態は FOIT(Flash of Invisible Text/文字が消えて見える現象)と呼ばれます。逆に代替フォントで先に表示してからWebフォントに差し替えると、書体が入れ替わる瞬間が見えます。これが FOUT(Flash of Unstyled Text)です。

font-display: swap; は、この2つのうち FOUT を選ぶ指定です。読めない時間をほぼ作らないので本文向きですが、代替フォントとWebフォントで字幅が違うとレイアウトががたつきます。この揺れが気になる場合は、size-adjustascent-override でフォールバック側の見た目を寄せるか、装飾的な見出しなら optional にして差し替え自体を諦める、といった選び方になります。

style.css
@font-face {
  font-family: "MyFont";
  src: url("../fonts/myfont.woff2") format("woff2");
  font-display: swap; /* 待たずに代替フォントで表示し、届いたら差し替える */
}

なお font-display は表示の順番を決めるだけで、ダウンロードを早める指定ではありません。ファーストビューで確実に使うフォントなら、HTML 側で先読みを指示する方法もあります。フォントは常に匿名の CORS モードで取得されるため、同一ドメインのファイルでも crossorigin 属性が必要です。

index.html
<link rel="preload" href="/fonts/myfont.woff2" as="font" type="font/woff2" crossorigin>

unicode-range で必要な文字だけを読み込む

unicode-range は、その face を「どの文字に使うか」を Unicode のコードポイントで限定する記述子です。範囲は U+0041(1文字)、U+0030-0039(範囲)、U+30??? は任意の桁)の3つの書き方があり、カンマで並べられます。省略したときの既定値は U+0-10FFFF、つまりすべての文字です。

重要なのは、ページ内に範囲内の文字が1つも無ければ、そのフォントファイルはダウンロードされないという点です。これを利用すると、フォントを文字の範囲ごとに分割(サブセット化)して、実際に使われた分だけを取りに行かせられます。

下のデモは、半角数字だけを別の書体にしたものです。"DigitFont"U+0030-0039 しか担当していないので、数字以外の文字は font-family の次に書いた sans-serif で表示されます。範囲を U+0041-005A(大文字のA〜Z)などに書き換えて、担当する文字が変わる様子を試してみてください。

<p class="price">合計 12,800 円(税込)</p>
<p class="note">数字の 0123456789 だけ別の書体になります</p>
/* 半角数字(U+0030〜U+0039)にだけ使われる face として登録する。
   ここでも src は local() なので、ダウンロードは発生しない */
@font-face {
  font-family: "DigitFont";
  src: local("Courier New"), local("Menlo"), local("Consolas");
  unicode-range: U+0030-0039; /* 0 から 9 まで */
}

body {
  margin: 12px;
  font-family: sans-serif;
}

/* 数字以外は unicode-range の外なので、後ろの sans-serif に回る */
.price {
  font-family: "DigitFont", sans-serif;
  font-size: 26px;
}

.note {
  font-family: "DigitFont", sans-serif;
  color: #6b7280;
}
Preview

日本語フォントで効果が大きい理由

欧文フォントは収録文字が数百字程度ですが、日本語フォントは漢字・かな・記号を含めて数千字から数万字を収録するため、1ファイルが数 MB になることも珍しくありません。これを丸ごと読み込むと、そのページで実際に表示するのは数十字だけ、ということが起こります。

そこでフォントを文字コードの範囲ごとに何十個ものファイルに分割し、それぞれに unicode-range を付けて登録しておきます。すると、ページに出てくる文字が含まれるファイルだけがダウンロードされ、通信量を大きく減らせます。Google Fonts の日本語フォントが配信している CSS も、この方式で細かく分割された @font-face が並んだ形になっています。自前のフォントで同じことをするには、フォントをサブセット化するツールで分割し、分割後の範囲に合わせて unicode-range を書く必要があります。

Webフォントが表示されないときに見る場所

指定したはずのフォントにならないときは、まずブラウザーの開発者ツールを開きます。ネットワークタブでフォントファイルのリクエストが出ているか、出ているならステータスコードは何かを見ると、原因が「ファイルに届いていない」のか「届いているが使われていない」のかで切り分けられます。

url() のパスは CSS ファイルからの相対になる

404 が返っているときは、ほぼパスの誤りです。ここで間違えやすいのは、@font-faceurl() に書いた相対パスがHTML ファイルではなく、その CSS ファイルの場所を基準に解決されることです。/assets/css/style.css から /assets/fonts/myfont.woff2 を読むなら url("../fonts/myfont.woff2") になります。HTML の階層が深いページと浅いページで表示が変わる、といった症状が出たときは、ルートからの絶対パス(url("/assets/fonts/myfont.woff2"))にすると迷いません。

別ドメインに置いたフォントは CORS の許可が要る

フォントファイルは画像と違い、常に CORS の仕組みを通して取得されます。そのため、CDN や別サブドメインなどページとは異なるオリジンに置いたフォントは、配信側が Access-Control-Allow-Origin ヘッダーを返さないと使えません。この場合、ファイル自体は 200 で返ってきているのにフォントだけ適用されず、コンソールに CORS のエラーが出ます。サーバー側の設定を直すか、フォントを自サイトと同じオリジンに置くのが解決策です。ローカルで file:// のまま HTML を開いている場合も同じ理由で読み込めないことがあるので、簡易サーバーを立てて http:// で確認します。

format() の指定と実ファイルが食い違っている

format() はブラウザーがダウンロード前に読み飛ばす判断に使うため、.ttf のファイルに format("woff2") と書くと、対応していると判断されたのに中身が解釈できず失敗します。逆に、実際は .woff2 なのに format("truetype") と書いてしまうケースもあります。拡張子と format() の対応は前掲の表のとおりで、.woff2 なら woff2.ttf なら truetype です。ファイルが壊れている場合も同じように読み込みだけ失敗するので、そのフォントファイルを単体でブラウザーにドラッグして開けるか確かめると切り分けられます。

font-weight の登録漏れで合成太字になる

フォントは表示されているのに、太字の見た目だけが妙ににじむ・つぶれる、というときは合成太字(synthetic bold)を疑います。ブラウザーは、指定された太さに合う face が登録されていないと、近い太さの face を選んだうえで字形を機械的に太らせて表示することがあります。これは本来のボールド書体とは別物で、線が均一に膨らむため見た目が濁ります。

典型的なのは、ボールドのファイルを登録するときに font-weight を書き忘れる(=400 として登録される)ケースと、可変フォントでレンジを書き忘れるケースです。逆に、ボールドのファイルを font-weight: 400 で登録してしまうと、太字でない文章まで太いまま表示され、そこにさらに font-weight: bold; が重なって二重に太くなります。@font-face ごとに、そのファイルが実際にどの太さなのかを確認して書き直してください。合成を明示的に止めたい場合は、使う側で font-synthesis: none; を指定する方法もあります。

まとめ

@font-face は、フォントファイルに好きな名前を付けて font-family から呼べるようにするルールです。必須の記述子は名前を決める font-family と、入手先を書く src の2つ。src には url()local() を並べられ、format() で形式を伝えます。いま新しく書くなら woff2 を軸にすれば十分です。太さや斜体は同じ名前で font-weight / font-style を変えて登録し、可変フォントなら font-weight: 100 900; のようにレンジで書きます。読み込み中の見え方は font-display で選び、本文なら FOIT を避ける swap が扱いやすい選択肢です。unicode-range で担当する文字を絞れば、その範囲の文字が使われていないファイルはダウンロードされないため、文字数の多い日本語フォントほど効果があります。うまく表示されないときは、開発者ツールでリクエストの有無とステータスを確認し、パス・CORS・format()font-weight の登録内容を順に見ていくと原因にたどり着けます。

参考ページ