ブラウザゲームでフォントを読み込む:表示崩れを防ぐ順番
遊びをつくる執筆にClaudeを使用しています。

ブラウザゲームでカスタムフォントを使うと、ゲームが起動した瞬間に文字がシステムフォントで表示され、数秒後に切り替わって画面がガタつく、という現象に悩まされることがあります。スコア表示やメニューのレイアウトが崩れ、プレイヤーに「壊れたゲーム」という印象を与えてしまいます。 この問題の原因は「フォントの読み込み完了を待たずにゲームを描画し始めること」です。ゲームの初期化コードとフォント読み込みの順番を正しく組むだけで、ほとんどのケースは解決できます。この記事では、崩れが起きるしくみを理解したうえで、Canvas・DOM・WebGL それぞれの状況に合わせた対処手順を示します。
この記事の目次
なぜフォントの読み込み順が崩れを引き起こすのか
ブラウザはHTMLを解析しながら並行してリソースを取得します。CSSに書いた @font-face は「フォントが必要になったとき」に初めてダウンロードを始める遅延読み込みが基本動作です。JavaScriptが DOMContentLoaded や window.onload のタイミングで描画を開始しても、フォントファイルの取得はまだ途中であることが多く、ブラウザはひとまずフォールバックフォントで文字を表示します。
Canvas APIの fillText() はこの問題がとりわけ顕著です。DOMと違い、Canvasは一度描いたピクセルを自動では再描画しません。フォントが到着した後も、明示的に再描画を呼ばない限り古いフォントで書かれた文字が残り続けます。一方、DOMベースのゲームはCSSのフォント置き換えが自動で起きるため見た目は更新されますが、レイアウトの再計算でボタン位置や文字サイズが一瞬ずれる「FOUT(Flash of Unstyled Text)」が発生します。
フォントの読み込み完了を検知する三つの方法
フォントが「使える状態」になったタイミングを確実につかむ手段を選ぶことが出発点です。
`document.fonts.ready` を使う
現代のブラウザが広くサポートする FontFaceSet APIの ready プロパティは、ページに宣言されたすべてのフォントの読み込みが完了したときに解決するPromiseを返します。ゲームの初期化関数を非同期にして、このPromiseを待つのが最もシンプルな方法です。
async function init() {
await document.fonts.ready;
startGame(); // フォント確定後に描画開始
}
init();ゲーム全体の起動を一か所で管理しているプロジェクトであれば、この一行を加えるだけで大半の崩れは消えます。
`FontFace` APIで個別に管理する
使うフォントが一種類で、CSSではなくJavaScriptでフォントを定義・読み込みたい場合は FontFace オブジェクトが有効です。読み込み完了のPromiseを直接受け取れるため、必要なフォントだけを待って起動できます。
const font = new FontFace('MyFont', 'url(myfont.woff2)');
font.load().then((loadedFont) => {
document.fonts.add(loadedFont);
startGame();
});CanvasでこのフォントをすぐGameに使う場合も、document.fonts.add() を呼ばないと ctx.font の指定が無視されてフォールバックが使われるので注意が必要です。
CSSの `font-display` で視覚的な崩れを和らげる
JavaScriptの制御が難しい状況、たとえばサードパーティのゲームエンジンが描画を管理していてフック箇所がない場合は、@font-face に font-display: block を指定することでフォントが届くまで文字を不可視にする(FLOATを回避する)選択肢があります。ただしこれは「崩れを見えなくする」手法であり、フォントの読み込みが遅いとそのぶん文字が表示されない時間が生まれます。読み込み速度の改善と組み合わせて使うのが現実的です。
Canvasゲームで実装する具体的な手順
Canvas主体のゲームで表示崩れを防ぐ手順を整理します。
- @font-face をHTMLの <head> に書く CSSファイルより <style> タグで直接書くほうが解析の遅延を減らせます。
- document.fonts.ready を await してから描画ループを開始する requestAnimationFrame の最初の呼び出しをこのPromiseの後に置きます。
- ローディング画面を挟む フォント待ちの間もプレイヤーに何かを見せるため、システムフォントで書いた「Loading...」や進捗バーを先に描いておきます。この部分はカスタムフォントを使わないと決めておけば、フォント待ちの影響を受けません。
- フォントの事前レンダリング(ウォームアップ)を行う Canvasでフォントを使う際、ブラウザによってはフォントがdocument.fonts.readyで完了していても最初の fillText 呼び出しで一瞬フォールバックが使われることがあります。起動時に画面外の座標(例:-9999px)で一度 fillText を呼んでおくと、フォントのレンダリングが内部キャッシュに乗り、以降の描画が安定します。
読み込み速度そのものを上げる判断基準
崩れを防ぐ「待つ設計」と並行して、フォントの届く速度を上げることが根本的な解決につながります。
フォーマット選択:woff2は圧縮効率が高く、モダンブラウザの対応も十分です。ttfやotfをそのまま配信するのは避け、woff2に変換してから使います。変換にはオープンソースのツール(woff2 コマンドラインツールなど)が使えます。
サブセット化:日本語フォントは全字種を含むと数MB単位になります。ゲームで実際に使う文字だけを抜き出した「サブセット」フォントを作ると、ファイルサイズを大幅に削減できます。表示するテキストが固定(「GAME OVER」「スコア」など)のゲームなら、使用文字を事前に洗い出しておくと効果が出やすい箇所です。
<link rel="preload"> の活用:HTMLの <head> に次の記述を加えると、ブラウザがHTMLを読み始めた直後にフォントのダウンロードを開始します。
<link rel="preload" href="myfont.woff2" as="font" type="font/woff2" crossorigin>crossorigin 属性はフォントの取得にCORSリクエストが必要なため、同一オリジンのフォントでも省略しないことが推奨されています。
Google Fontsなど外部サービスを使う場合:外部CDNからフォントを読み込む場合はネットワーク遅延が加わります。document.fonts.ready でも完了を待てますが、通信環境の悪いプレイヤーへの影響が大きくなるため、ゲームで使うフォントは自前で配信するか、サブセットをローカルに持つことを検討します。
よくある失敗と確認ポイント
「ローカルでは問題ないのに公開すると崩れる」:ローカルではキャッシュが効いているためフォントが即座に使えますが、初回アクセスではダウンロードが発生します。開発ツールのネットワークタブで「キャッシュを無効化」してリロードし、本番相当の状態を再現して確認します。
ctx.font の指定タイミング:Canvasでは ctx.font を設定した時点でフォントが未ロードだと、fillText が呼ばれた際にフォールバックに戻ります。document.fonts.ready を待っていても、ctx.font の再設定を描画ループの途中で行っている場合は同じ問題が起きます。フォント指定は描画の直前に毎フレーム行うか、フォントが変わらない場合は変更しないよう管理します。
モバイルでの初期描画が遅い:モバイルではCPU・メモリの制約からフォントのデコードに時間がかかることがあります。ローディング画面でフォントの事前レンダリングを行う設計が特に有効です。
次の行動
まず自分のゲームのエントリーポイントに await document.fonts.ready を一行追加し、ローカルで開発者ツールのキャッシュ無効化モードで崩れが消えるか確認してください。Canvasを使っている場合は起動時に画面外で fillText を一度呼ぶウォームアップも合わせて試します。フォントサイズが気になる場合は、使用文字のサブセット化を次のステップとして検討してください。完成したゲームはHTMLファイルやZIPファイルの形式でアソバースに投稿・公開できます。
参考資料
- MDN Web Docs — FontFaceSet: ready プロパティ
- MDN Web Docs — FontFace API
- MDN Web Docs — CSS @font-face / font-display
- web.dev — Best practices for fonts(Google)

