開発

Flutter Webで日本語が一瞬
文字化けする問題を
font-display:blockで解決した話

· SEADICE

Flutter WebのHTMLレンダラーはDartのFontLoaderではなくブラウザ自身のCSSフォントシステムで文字を描画するため、日本語フォントの読み込みが1テンポ遅れて豆腐や文字化けが一瞬見えることがある。原因はfont-display: swapで先に代替フォントのまま描画してしまうことで、CSS側のfont-display: blockとDart側でのフォント先読みを組み合わせる2段構えで解消した。

なぜDart側のFontLoaderだけでは間に合わないのか

Flutter WebはCanvasKitではなくHTMLレンダラーを使う場合、実際の文字描画をブラウザのCSSエンジンに委譲する。そのためDart側でFontLoader('NotoSansJP').load()を待っても、ウィジェットツリーの初回構築とブラウザのフォント読み込みタイミングは別物として扱われる。

font-display: swap と block の違い

swapは代替フォントで即座に描画してから本フォントに差し替えるため速く見えるが、日本語では一瞬の文字化けが必ず発生する。blockは本フォントの読み込みを待ってから描画するため、文字化けした状態がユーザーの目に触れない。

設定値初回描画文字化けの見え方
font-display: swap即座に代替フォントで描画本フォント読み込み完了まで文字化けが見える
font-display: block本フォント読み込み完了まで描画をブロック文字化けした状態が画面に出ない

2段構えの対策

CSS側で描画をブロックし、Dart側でもrunApp()より前にフォント読み込みを完了させることで、文字化けが表示される経路を両方とも塞ぐ。

  1. web/index.html<head><link rel="preload">でフォントを先読みし、@font-facefont-display: blockを指定
  2. Dart側のmain()WidgetsFlutterBinding.ensureInitialized()直後にFontLoaderで読み込み、await fontLoader.load()runApp()より前に完了させる
<link rel="preload" href="fonts/NotoSansJP-VariableFont_wght.ttf" as="font" type="font/ttf" crossorigin> <style> @font-face { font-family: 'NotoSansJP'; src: url('fonts/NotoSansJP-VariableFont_wght.ttf') format('truetype'); font-weight: 100 900; font-display: block; } </style>

手順4(CSS宣言)だけでも手順5(Dart側の先読み)だけでも、単独では文字化けが再現することがある。両方を揃えて初めて「文字化けゼロ」が安定する。

39本のアプリ全てに同じパターンを徹底する

この対策はSEADICEの全Flutterアプリ共通のチェックリストとして固定化し、新規アプリを作るたびに同じ手順を機械的に繰り返すだけにしている。

Related

新規アプリ公開後の「配線忘れ」をレジストリで自動化した話 →

← ブログ一覧に戻る