Closed13

scriptでweb fontを読み込む時の方法とかまとめ

fujifuji

目標

TypeSquareやFont Plusなど、scriptでweb fontを読み込むサイトの利用方法があまりまとめられていないので、ざっくりここにまとめて最終的に記事にする

特に動的取得やssr, spaの場合、サイト側が提供するjs apiを駆使しないとダメとなるので、そこも確認しまとめる

対象サイト

  • TypeSquare
  • Font Plus

対象フレームワーク

  • nuxt
  • next
  • astro
  • remix
    etc...
fujifuji

Font Plus

https://fontplus.jp/home

プラン(2024/12/4 時点)

無料トライアル= スマートライセンス6ヶ月分

無料トライアルについて無料トライアルは、フォントプラスの「スマートライセンス」を無料でお試しいただけるサービスです。 スマートライセンスは複数サイトにて5000PV(有効期限6か月)まで、ご利用いただけます。

スマートライセンスはこんな感じ

使う文字だけを読み込む効率的なフォント配信方式

が大事で、動的な文字列に対してはフォントが当てられない

詳しくはこちら
https://blog.bi3.jp/blog/2020/11/26/webfont-defer/

fujifuji

Font Plus 使い方

会員登録は必須


マイフォントに利用したいフォントを登録する


スクリプトの発行を開く

スクリプトをサイトに設置する
※cssなどstyleでfont familyを設定する必要がある

nextの場合

layout.tsx に scriptを貼っつける

    <html lang="ja">
      <head>
        {/* font plusのscript */}
        <script src="https://webfont.fontplus.jp/accessor/script/fontplus.js?xxxxxxxxx%3D&box=11w4ujahMPo%3D&aa=1&ab=2"></script>
      </head>

      <body>{children}</body>
    </html>

これだけで動くには動くがエラーとなる

Synchronous scripts should not be used. See: https://nextjs.org/docs/messages/no-sync-scripts

eslintの警告

Nextではパフォーマンスの関係上、<Script>を利用するか、async, deferscriptに設定する必要がある

A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:

- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.

It can also happen if the client has a browser extension installed which messes with the HTML before React loaded

ブラウザでのエラー

React のハイドレーション (hydration) プロセス中に、サーバーが生成したHTMLとクライアントが再レンダリングした HTMLが一致ていない

font plusのjsによってdomの操作が行われているのが原因


解決としては <Script>を利用する or asyncを付与して、後から自分たちで明示的に読み込む

fujifuji

scriptの挙動

script

scriptasyncdeferをつけた時、つけてない時の挙動
(ChatGPTに尋ねてみた)

わかりやすい記事もあった
https://www.wakuwakubank.com/posts/614-javascript-async-defer/

なにもつけていない

  • ダウンロードタイミング: HTMLの解析中にスクリプトのダウンロードが始まる
  • 実行タイミング: スクリプトのダウンロードが完了した時点で、HTMLの解析が一時停止し、スクリプトを即座に実行する
  • 影響: HTMLの解析がブロックされるため、ページのロード時間が延びる可能性がある

async

  • ダウンロードタイミング: HTMLの解析と並行してスクリプトをダウンロードする。
  • 実行タイミング: ダウンロードが完了次第、HTMLの解析を一時停止してスクリプトを即座に実行する。
  • 影響: スクリプトの実行順序は ダウンロード完了順 になり、HTMLの解析が中断する可能性がある。
  • 用途: 他のスクリプトに依存しない、独立したスクリプト(例: 広告スクリプト、分析ツール)。

defer

  • ダウンロードタイミング: HTMLの解析と並行してスクリプトをダウンロードする。
  • 実行タイミング: HTMLの 完全な解析が終わった後 に実行される(DOMContentLoaded イベント前)。
  • 影響: スクリプトの実行順序は HTMLに記述された順序 が保証される。
  • 用途: HTML構造に依存するスクリプト(例: DOM操作スクリプト)。
fujifuji

scriptでなにもつけてない、deferをつけた場合は、前述通りハイドレーションエラーになるがフォントは読み込める
asyncの場合は、明示的に読み込めばフォントも反映された

asyncの場合でも自動で読み込まれるはずだけど、そうでないのはnextだからかもしれない…?

大人しく Scriptを使っておくのが良さそう

fujifuji

Scriptを使う場合はこんな感じ

// onLoadを利用するには "use client"の記述が必要
   <Script
      src="https://webfont.fontplus.jp/accessor/script/fontplus.js?xxxxxxxxx%3D&box=11w4ujahMPo%3D&aa=1&ab=2"
      strategy="afterInteractive" // default
      onLoad={() => {
        if (FONTPLUS) {
          FONTPLUS.reload(false);
        }
      }}
    />

strategyはいくつか種類があるが afterInteractive(初期値)で良さそう?
試したところどれも自動で読み込んでくれなかったので明示的な読み込みが必要だった
この場合にonLoadを利用できるafterInteractiveが記述をまとめられるので楽そう

って思ったけどページ遷移時の考慮を考えるとそうでもないかも& use clientの記述必要になって layout.tsxに配置できないので微妙かも?


これだけだと初回の時しか読み込まれない(=ページ遷移するとフォントが反映されない)
ので、修正

layout.tsx
    <html lang="ja">
      <head>
        {/* font plusのscript */}
        <Script
          src="https://webfont.fontplus.jp/accessor/script/fontplus.js?xxxxxxxxx%3D&box=11w4ujahMPo%3D&aa=1&ab=2"
          strategy="beforeInteractive"
        />
      </head>

      <body>
        {children}

        {/* font plusを自分たちで明示的に読み込む */}
        <FontLoader />
      </body>
    </html>
font-loader.tsx
"use client";

import { usePathname } from "next/navigation";
import { useEffect } from "react";

export const FontLoader = () => {
  const pathname = usePathname();

  useEffect(() => {
    if (window.FONTPLUS) {
      window.FONTPLUS.reload(false);
    }
  }, [pathname]);
  return null;
};

beforeInteractiveでサーバーサイド側であらかじめ読み込んでおいて、パスの変更に応じてクライアント側で読み込む

ページ遷移ごとにPVが発生したり、動的な文字列には対応していない

fujifuji

動的な文字列対応

FONTPLUS.reload(false), window.FONTPLUS.reload(false)を行うだけ

もしくは、見えないように必要な文字列をセットしておけばいけるらしい(漢字とか含めるとすごいことになりそうなので、cdnで読み込めるようにプランを変えた方が良さそう…)
<div style="display:none">ダミー文字0123456HOGEhoge</div>

https://fontplus.jp/faq/4101

fujifuji

Nuxt

nextと違ってheadに追加するだけでOK

  app: {
    head: {
      script: [
        {
          src: "https://webfont.fontplus.jp/accessor/script/fontplus.js?xxxxxxx%3D&box=11w4ujahMPo%3D&aa=1&ab=2",
        },
      ],
    },
  },

SSRの場合はこれで反映される
SPAの場合は明示的に読み込む必要あり

onMounted(() => {
  FONTPLUS.reload(false);
});

next同様で、ページ遷移や動的な要素には対応していないので別途対応が必要となる

fujifuji

RemixはSPA、SSRともに試してみる

fujifuji

Remix(SPA)

<script
  src="https://webfont.fontplus.jp/accessor/script/fontplus.js?XXXXXX%3D&box=11w4ujahMPo%3D&aa=1&ab=2"
></script>

Layoutscriptを追加する。

Remix(SSR)

<script
  src="https://webfont.fontplus.jp/accessor/script/fontplus.js?XXXXXX%3D&box=11w4ujahMPo%3D&aa=1&ab=2"
  async
></script>

Layoutscriptを追加する。asyncをつけないとフォントが反映されない

SPA, SSRともに

export default function App() {
  const { pathname } = useLocation();

  useEffect(() => {
    if (window.FONTPLUS) {
      window.FONTPLUS.reload();
    }
  }, [pathname]);

  return <Outlet />;
}

topのApp部分でreloadを実行する

fujifuji

Astor(SSG)

astroの場合は、is:inlineをつけるだけ

<script is:inline src="https://webfont.fontplus.jp/accessor/script/fontplus.js?XXXXXXXX%3D&box=11w4ujahMPo%3D&aa=1&ab=2"></script>

is:inlineをつけないとastroの最適化処理が入ってしまって動かなくなる

SSGなのでページ遷移時のフォント再読み込みは不要
ClientRouter を利用する場合は必要

<script>
	  const loadFont = () => {
    if (window.FONTPLUS) {
      window.FONTPLUS.reload(false);
    }
  };

  document.addEventListener('astro:after-swap', loadFont);
</script>

https://docs.astro.build/ja/guides/view-transitions/#ページナビゲーション中のスクリプトの動作

ドキュメントの通りだと astro:page-loadastro:after-swapの2種類でルーティングのページアニメーションを検知できるらしい

どちらでも問題ないけど、適切そうなのはastro:after-swapなので採用

https://zenn.dev/yamatoiizuka/articles/9d24796bfa5082

fujifuji

font-smooth について

web fontを適応すると、sampleより表示が太く見える場合がある
これはブラウザやOSによってアンチエイリアスの処理が異なるから

それを調節できるのが font-smoothing

  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

基本的にはこれを指定しておけば問題ない

このスクラップは2025/01/09にクローズされました