North Detail / ノースディテール

BLOG ブログ

ブログ
CATEGORY
TECH

そろそろWebフォントのFOUT問題・Web Vitals対策をがっちりと検証しておこう(SEO・読み込み方法・サブセット化・ユーザビリティなど)

弊社サイトでは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 ※1 対策と、画面表示の際に 一瞬チラつく ※2 問題を解消するためには、Webフォントをどのように実装するのがベストなのかを、実際に本番環境へ反映し検証してみました。

検証の最終目標は下記となります。

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

※優先順位はありませんがイイ感じに折り合いが付くように、検証と調整を行う前提

  1. 2021年には『検索結果順位に影響を与える指標』と明言されており、Webサイトにおけるユーザー体験向上を目指して Google が導入した指標
    主に下記3項目に分かれる
    • ページの表示速度@LCP(Largest Contentful Paint)
    • ユーザー操作への反応性@FID(First Input Delay)
    • 視覚要素の安定性@CLS(Cumulative Layout Shift)
  2. FOUT(Flash Of Unstyled Text)のことで
    直訳すると『イケてないフォントが一瞬見えちゃうヤーツ』
    画面表示時にデバイスフォントから、読込完了したWebフォントに切り替わる際に発生する事象
    • 登録文字数が多い(重たい)日本語フォントでは悩みどころ
    • Material+Iconsなどの特定文字列をピクトグラムに置換するフォントだと、長い文字列が一瞬表示されるだけでレイアウト崩れも起きて致命的

検証その1.fonts.googleapis.com から読み込む

説明するまでもないと思いますが、日本語 NotoSans を例として実装方法を解説します。

Google Fonts

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

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 にて読み込むのが、正しいと今回の検証では判断しました。

フォントファイルのダウンロード

Google Fonts

上記サイトへアクセス後、使用するフォントを検索し詳細画面を表示させ、画面右上の方にある 『Download family』 を押下し圧縮ファイル(.zip)をダウンロードします。
解凍すると otf(OpenTypeFont)ファイルライセンステキスト が展開されます。
ちなみに解凍された otf (日本語 NotoSans)ファイルサイズは 4,684Kb ありました…日本語フォント恐るべし

フォントファイルの作成

続いては『拡張子を何にするか問題』です。

woff ファイルに対応するブラウザ

woff2 ファイルに対応するブラウザ

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

WOFFコンバータ

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

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

サブセットフォントメーカー

上記アプリケーションを使用して 第一水準漢字・記号・ローマ字・カタカナ・ひらがな までに絞り込んだ結果、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>

preload を認識するブラウザ

上記URL で確認できる通り rel="preload" を認識できないブラウザは、woff2 を認識できませんので 『preload するのは woff2 のみで良い』 と判断でき、上記のように woff2 のみ preload しました。(実際には3種類・8タイプの Webフォントを先読みしています)

メリット

  • FOUT がほぼほぼ認識できなくなった!
  • リクエスト数がかなり減った(Google Fonts サブセットを使用しなくなったため)

デメリット

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

数値的比較

Lighthouse 測定値一覧

fonts.googleapis
検証その1
Web Font Loader
検証その2
preload
検証その3
Performance96点100点96点
First Contentful Paint0.5s0.4s0.3s
Speed Index0.9s0.7s0.7s
Largest Contentful Paint1.3s0.6s1.4s
Time to Interactive0.5s0.4s0.3s
Total Blocking Time0ms0ms0ms
Cumulative Layout Shift0.0150.0080.007

パッと見ではどのように実装しても大差はないように思います。 ※『Performance』以外は数値が小さいほど優秀 大きく違うのは 『preload』 の場合は GoogleCDN を使用していないという点で、Google さんのチカラを借りずに、重たくて表示速度改善には不向きといわれる 日本語 Webフォント を自サーバからダウンロードさせていてこの数値ということです。
デフォルトのフォントセットを使用した時は『Speed Index』『Largest Contentful Paint』『Time to Interactive』あたりの数値が大きく悪化しましたが、サブセット化施策後は低く抑えることができています。

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

まとめ

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

nanba
WRITER:nanba
高い技術力を誇る『NorthDetail』内では珍しい『チカラワザ系コーダー』
主な記事 一覧へ

一覧へ

IS 501383 / ISO 27001