WebフォントのFOUT問題・Web Vitals対策を検証してみた

# いろんな意味での良いWebフォント表示方法

:link: [オリジナルページはこちら](https://www.northdetail.co.jp/blog/1324/)

弊社サイトでは3種類のWebフォント(font-weight的には8タイプ)を使用しております。
**【'Noto+Sans+JP:400,700', 'Noto+Sans:400,700', 'Raleway:200,400,500,700'】**
全てライセンスフリーの Google Fonts です。
Googleさん！ありがとう！！
ひじょうに助かります。お世話になっております。足を向けて寝られません！

その Google のエラい人が、来年あたりに検索結果に反映させると明言している **` Web Vitals `** <sup>※1</sup> 対策と、画面表示の際に **` 一瞬チラつく `** <sup>※2</sup> 問題を解消するためには、Webフォントをどのように実装するのがベストなのかを検証してみました。

優先順位はありませんがイイ感じに折り合いが付くように、検証と調整を行う前提で最終目標は下記となります。

* **チラつきを極力なくす**(ユーザビリティ)
* **Web Vitals 数値をイイ感じにする**(SEO)

## Web Vitals
2021年には『検索結果順位に影響を与える指標』と明言されており、Webサイトにおけるユーザー体験向上を目指して **Google が導入した指標**<br>主に下記3項目に分かれる

* ページの表示速度**＠LCP(Largest Contentful Paint)**
* ユーザー操作への反応性**＠FID(First Input Delay)**
* 視覚要素の安定性**＠CLS(Cumulative Layout Shift)**

## 一瞬チラつく
**FOUT(Flash Of Unstyled Text)**のことで直訳すると『イケてないフォントが一瞬見えちゃうヤーツ』
画面表示時にデバイスフォントから、読込完了したWebフォントに切り替わる際に発生する事象

* 登録文字数が多い(重たい)日本語フォントでは悩みどころ
* Material+Iconsなどの特定文字列をピクトグラムに置換するフォントだと、長い文字列が一瞬表示されるだけでレイアウト崩れも起きて致命的

## 検証その1.fonts.googleapis.com から読み込む
説明するまでもないと思いますが、日本語 NotoSans を例として実装方法を解説します。
:link: [Google Fonts](https://fonts.google.com/)

1. 上記にアクセスして使用するフォントを検索
1. 詳細画面が表示されたら、使用するfont-weightを選択<br>【+Select this style】を押下
1. 右カラム内の Embedタブを押下で貼り付け方法表示(下記参照)
1. 諸々を適材適所にコピペ

head 内にて linkタグで読み込む場合

```
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet">
```

css から import で読み込む場合(超遅いから非推奨)

```
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap");
```

css での font-family 設定

```
font-family: "Noto Sans JP";
```

### メリット
* Web Vitals 的には優秀！さすが Google！任せて安心！
* 実装の手軽さが秀逸(VIVA コピペ！)

### デメリット
* FOUT がハッキリ認識できる程度感じる
* woff2 に関してはファイル分割(サブセット化)のせいでリクエスト数がかなり多くなる

## 検証その2.Web Font Loader
JavaScript を利用して非同期読み込みするヤーツ

```
WebFontConfig = {
  google: { families: ['Noto+Sans+JP:400,700', 'Noto+Sans:400,700', 'Raleway:200,400,500,700'] }
};
(function() {
  var wf = document.createElement('script');
  wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
    '://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js';
  wf.type = 'text/javascript';
  wf.async = 'true';
  var s = document.getElementsByTagName('script')[0];
  s.parentNode.insertBefore(wf, s);
})();
```

2行目の ` families ` で読み込むフォントを指定します。
上記見本のようにフォントや font-weight は『,(カンマ)』繋ぎで複数指定できます。

Webフォント読み込み終了時、html に class名**『wf-active』**が付与される仕様となっており、FOUT 対策として読み込みが終了してからテキストを表示するようにcssにて設定をしてみた。

```
html{
  visibility:hidden;
}
html.wf-active{
  visibility:visible;
}
```

当然、通信環境にも左右されるとは思いますが、我が家の環境では非表示時間も一瞬ですし、画面表示時のチラつきは全くなくなり、個人的にはストレスを感じなくなりました！
**「完璧じゃないっすかコレ！」**って思ったのですが…Google さんから激しく怒られました…
**「ユーザビリティとか通信環境とか考えて！テキストを非表示させるとか、マジありえないからっ！真っ白って！」**ってニュアンスで＞＜
### メリット
* Web Vitals 的には抜群！さすがGoogle！まかせて良かった！
* 実装が手軽(コピペ万歳！)
* 非表示にすることで、そもそもFOUT 問題解消？

### デメリット
* Webフォント読み込み終了まで非表示にしたら Google さんにメッチャ怒られた…
* 上記非表示設定を止めたら… **FOUT が前よりも長く感じて萎えた**
* woff2 に関してはファイル分割(サブセット化)のせいでリクエスト数がかなり多くなる

## 検証その3.preload を利用してフォントファイルを先読み
` rel="preload" ` を head 内で設定することで、ブラウザの主なレンダリング機構が起動する前(画面描画ライフサイクルの早期)から、非同期読み込みができ、レンダリングをブロックしにくくなります。

実装の段取りとして、まずは先行して読み込むフォントファイルをどうするか？という話になります。
Google Fonts のフォントファイル(woff とか eot とか)は **fonts.gstatic.com** にあるようですが、仕様変更などによって**流動的な URL** であることと、woff2ファイルに関しては**相当数分割(サブセット化)**されていることから、基本的には自サーバにフォントファイルを配置し ` @font-face ` にて読み込むのが、正しいと今回の検証では判断しました。
### フォントファイルのダウンロード
:link: [Google Fonts](https://fonts.google.com/)
上記サイトへアクセス後、使用するフォントを検索し詳細画面を表示させ、画面右上の方にある**『Download family』**を押下し圧縮ファイル(.zip)をダウンロードします。
解凍すると **otf(OpenTypeFont)ファイル** と **ライセンステキスト** が展開されます。
ちなみに解凍された otf (日本語 NotoSans)ファイルサイズは **4,684Kb** ありました…日本語フォント恐るべし
### フォントファイルの作成
続いては**『拡張子を何にするか問題』**です。

:link: [woff ファイルに対応するブラウザ](https://caniuse.com/?search=woff)
:link: [woff2 ファイルに対応するブラウザ](https://caniuse.com/?search=woff2)
対応ブラウザだけから判断すると woff ファイルだけでも良いように思いますが、woff2 ファイルだと更に10％以上のファイルサイズダイエットが出来るようですし、今後は woff2 ファイルに対応するブラウザは増えていくと予想出来ましたので、弊社サイト対象ブラウザバージョンとの兼ね合いなども考慮して、` woff・woff2 ` としました。

:link: [WOFFコンバータ](https://opentype.jp/woffconv.htm)
上記アプリケーションを使用して otf から woff にコンバートした結果、ファイルサイズは 1,885Kb まで落ちました。
半分以下になったものの、弊社サイトでは日本語フォントを**『400(normal)』**と**『700(bold)』**の2種類を使用しているため、純粋に日本語フォントを表示させるためだけに **3.5Mb** ものファイルをダウンロードさせなければなりません＞＜
実際にこの状態で検証してみたところ**『Speed Index』『Largest Contentful Paint』『Time to Interactive』**このあたりの数値が強烈に悪化しました…もちろん Web Vitals 的にマイナスです。

当然、流れ的に 日本語フォントのサブセット化(ファイルサイズダイエット) を施策しました。
サブセット化とは『使わないであろう文字をフォントファイルの中から除外する』ってことです。
普通に考えたら難しそうな作業っぽいですが、世の中にはとても便利なアプリケーションがあり、またそれを無料で提供してくださる徳の高い方々がいらっしゃいます。

:link: [サブセットフォントメーカー](https://opentype.jp/subsetfontmk.htm)
上記アプリケーションを使用して **第一水準漢字・記号・ローマ字・カタカナ・ひらがな** までに絞り込んだ結果、**1,885Kb** から **555Kb** まで落ちました！
これはかなり現実的なラインです。

### 実装した記述

```
<link rel="preload" as="font" href="/fonts/NotoSansJP700.woff2" crossorigin>
<style>
@font-face {
  font-family: 'Noto Sans JP';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/NotoSansJP700.woff2') format('woff2'),
       url('/fonts/NotoSansJP700.woff') format('woff');
}
</style>
```

:link: [preload を認識するブラウザ](https://caniuse.com/?search=preload)
上記URL で確認できる通り ` rel="preload" ` を認識できないブラウザは、woff2 を認識できませんので **『preload するのは woff2 のみで良い』**と判断でき、上記のように woff2 のみ preload しました。(実際には3種類・8タイプの Webフォントを先読みしています)
### メリット
* **FOUT がほぼほぼ認識できなくなった！**
* リクエスト数がかなり減った(Google Fonts サブセットを使用しなくなったため)

### デメリット
* 実装自体そこそこ手間がかかる
* サブセットから外れた文字は Webフォントが適用されない

## 数値的比較
### Lighthouse 測定値一覧
||fonts.googleapis<br>検証その1|Web Font Loader<br>検証その2|preload<br>検証その3|
|:-------:|:--------:|:-------:|:-------:|
|**Performance**|96点|100点|96点|
|**First Contentful Paint**|0.5s|0.4s|0.3s|
|**Speed Index**|0.9s|0.7s|0.7s|
|**Largest Contentful Paint**|1.3s|0.6s|1.4s|
|**Time to Interactive**|0.5s|0.4s|0.3s|
|**Total Blocking Time**|0ms|0ms|0ms|
|**Cumulative Layout Shift**|0.015|0.008|0.007|
パッと見ではどのように実装しても大差はないように思います。
※『Performance』以外は数値が小さいほど優秀
大きく違うのは **『preload』** の場合は GoogleCDN を使用していないという点で、Google さんのチカラを借りずに、重たくて表示速度改善には不向きといわれる **日本語 Webフォント** を自サーバからダウンロードさせていてこの数値とうことです。
デフォルトのフォントセットを使用した時は**『Speed Index』『Largest Contentful Paint』『Time to Interactive』**あたりの数値が大きく悪化しましたが、サブセット化施策後は低く抑えることができています。

そして、なにより**『preloadを利用してフォントファイルを先読み』**を実装した一番の収穫ポイントは**『チラつき( FOUT )を極力なくす』**ことに成功したことですね！
SEO 的速度改善は勿論大事ですが、画面表示した際に**『チラつく』『カクつく』**方が個人的には気になっていたので(￣^￣)ゞ

## まとめ
本記事はあくまでも弊社サイトにて、実際に検証してみた所感・数値をまとめたものです。
検証内容に関してもザックリと3つに分けておりますが、細かく分けるとかなりのトライ・アンド・エラーを繰り返しております。(関係ないけど『トライアルアンドエラー』が正しいらしい)
そして、全てのサイトに当てはまる結果ではありません。
優先順位によっても違った実装方法になると思いますし、サイト特性や使用フォントによっても然りです。
なので、1つの参考程度にお読みいただけたらと思います。