display=swap では、まず代替フォントを表示し、Webフォントをダウンロードしたら、随時スワップするという挙動になる。この場合、代替フォントからWebフォントへ切り替わる FOUT (flash of unstyled text) が起こってしまう。こんな感じ↓

 出典:font-face descriptor playground

まぁ何も表示されないよりかは良いかと思うわけだが、時は流れ、最近ではWebの指標として、Web Vitalsというものがある。その中のCLS(Cumulative Layout Shift)では、レイアウトの安定性というのも評価する。つまり、代替フォントからWebフォントへ切り替わる際のレイアウトのズレ・ちらつきが、このCLSを下げてしまう原因になる。

ということで、今回紹介する font-display: optional の場合はどうなるかだが、100msのブロック期までにWebフォントを取得できたらWebフォントを表示する、できなかったら代替フォントを表示するという分かりやすい挙動。

この場合、FOUTはないが、最初に100msのブロック期があるので不可視テキストが表示される FOIT(Flash of Invisible Text) が起こってしまう。

と思っていたら、Chrome 83で改善が行われ、<link rel="preload"> と一緒に使うことで、ブロック期に不可視テキストを表示せず、というかレンダリング自体をブロックして、100ms後に一気に表示することでレイアウトのカタツキを無くすことにしているようだ。

 <!-- HTML -->
 <link rel="preload" href="NotoSansJP-Regular.woff2" as="font" type="font/woff2" crossorigin />
/* CSS */
@font-face {
  font-family: 'Noto Sans JP';
  font-style: normal;
  font-weight: 400;
  src: local('Noto Sans JP'), url(NotoSansJP-Regular.woff2) format('woff2');
  font-display: optional;
}

こんな感じに、fontファイルをpreloadして、optional設定すれば、レイアウトジャンクなしにWebフォントを読み込める。

Google Fontsから読み込む場合の最善手

<link rel=”preconnect” href=”https://fonts.gstatic.com” crossorigin /> <link rel=”preload” as=”style” href=”$CSS&display=swap” /> <link rel=”stylesheet” href=”$CSS&display=swap” media=”print” onload=”this.media=’all'” />

$CSS のところに自分の読み込みたいフォントのURLが当たる。まず最初に、woffファイルなどの配信元であるfonts.gstatic.comに事前に接続しておく。

そいで、 display=swapでスタイルシートをpreloadしておく。

そいで、最後にGoogle Fontsのスタイルシートを設定するのだが、見慣れない記述がある。

media="print" onload="this.media='all'"

ちょっまっ!印刷用CSSになってんじゃん!と思うじゃん。それでいい。ブラウザはこのCSSを印刷メディアのCSSだと理解して、現在のレンダリングと無関係に読み込み始める。これが狙いだ。そして読み込み終わったら現在のメディアに適用するといったことをしている。

これが一番簡単に非同期でCSSを読み込めるハックっぽい。display=optionalではないのは、この読み込み方と相性が悪いためだそうだ。

https://csswizardry.com/2020/05/the-fastest-google-fonts/

https://t32k.me/mol/log/optimize-webfont-loading/