Flutter最佳实践:Web项目中文字体首次加载乱码问题解决

一、问题本质(关键)

用flutter 开发 web项目时,中文字体先乱码再出现,根本原因是渲染流程导致的问题。

流程是这样的:

复制代码
页面渲染
  ↓
系统 fallback 字体(Roboto / sans-serif)
  ↓
NotoSansSC
  ↓
全局字体替换
  ↓
UI 重排 + "闪一下"

知道了问题来源,就知道了解决办法:预加载字体。

二、解决方案

Roboto 保持英英文字母和数字效果各平台统一。

Noto Sans SC 字体用于支持中文。

js 复制代码
  fonts:
    - family: Roboto
      fonts:
        - asset: assets/fonts/Roboto-Regular.ttf
        - asset: assets/fonts/Roboto-Medium.ttf
          weight: 500
    - family: Noto Sans SC
      fonts:
        - asset: assets/fonts/NotoSansSC-Regular.ttf
        - asset: assets/fonts/NotoSansSC-Medium.ttf
          weight: 500

方案 1: 提前 preload 字体

在 web/index.html:

js 复制代码
 <link rel="preload" href="../assets/fonts/NotoSansSC-Regular.ttf" as="font" type="font/ttf" crossorigin>
  <link rel="preload" href="../assets/fonts/NotoSansSC-Medium.ttf" as="font" type="font/ttf" crossorigin>
  <link rel="preload" href="../assets/fonts/Roboto-Regular.ttf" as="font" type="font/ttf" crossorigin>
  <link rel="preload" href="../assets/fonts/Roboto-Medium.ttf" as="font" type="font/ttf" crossorigin>

作用:

  • 提前进入下载队列
  • 减少首屏等待时间
  • 明显降低闪烁概率

方案 2: 提前 preload 字体

dart 复制代码
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await AppFontLoader.preload();//字体预加载
  runApp(const MyApp());
}

app_font_loader.dart

dart 复制代码
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

/// Web 端字体预加载
class AppFontLoader {
  AppFontLoader._();

  /// Web 端在首帧绘制前注册字体;配合 index.html 的 preload 命中浏览器缓存
  static Future<void> preload() async {
    if (!kIsWeb) {
      return;
    }
    await Future.wait([
      _loadFont(
        family: 'Noto Sans SC',
        assets: [
          'assets/fonts/NotoSansSC-Regular.ttf',
          'assets/fonts/NotoSansSC-Medium.ttf',
        ],
      ),
      _loadFont(
        family: 'Roboto',
        assets: [
          'assets/fonts/Roboto-Regular.ttf',
          'assets/fonts/Roboto-Medium.ttf',
        ],
      ),
    ]);
  }

  static Future<void> _loadFont({
    required String family,
    required List<String> assets,
  }) async {
    final FontLoader loader = FontLoader(family);
    for (final String asset in assets) {
      loader.addFont(rootBundle.load(asset));
    }
    await loader.load();
  }
}

三、使用

dart 复制代码
class AppThemeService extends ChangeNotifier {
  static final AppThemeService _instance = AppThemeService._();

  AppThemeService._();

  factory AppThemeService() => _instance;

  /// 全平台使用本地 Noto Sans SC,避免 Web CDN 与系统字体名不一致
  static const String appFontFamily = 'Noto Sans SC';

  /// Web 端西文回退到本地 Roboto
  static List<String>? get appFontFamilyFallback => AppPlatform.isWeb ? const ['Roboto'] : null;

......

  ThemeData get lightTheme => ThemeData(
        useMaterial3: false,
        brightness: Brightness.light,
        fontFamily: appFontFamily,
        fontFamilyFallback: appFontFamilyFallback,

......

  ThemeData get darkTheme => ThemeData(
        useMaterial3: false,
        brightness: Brightness.dark,
        fontFamily: appFontFamily,
        fontFamilyFallback: appFontFamilyFallback,

最后、总结

特殊情况相下比如:

SnackBar / Dialog / Overlay 中的字体可能因为Overlay + Web 字体异步加载 + TextTheme 未完全接管导致的链路断裂,无法使用 fontFamily 和 fontFamilyFallback。这时候需要单独处理一下,TextStyle后边调用 .copyWithFontFamily()即可。

dart 复制代码
extension TextStyleFontFamilyExtension on TextStyle {
  /// 设置字体家族
  TextStyle copyWithFontFamily() {
    return copyWith(
      fontFamily: AppThemeService.appFontFamily,
      fontFamilyFallback: AppThemeService.appFontFamilyFallback,
    );
  }
}
相关推荐
鬼手点金2 小时前
nanoGPT/nanochat 能做出 DeepSeek/Qwen 这样的大模型吗?
服务器·前端·javascript·bug·openclaw
蛋蛋的就会顺顺的2 小时前
hot100 p0——栈,贪心算法,动态规划
java·前端·javascript·算法·leetcode·力扣
程序员Sunday2 小时前
TypeScript 的 any、unknown、never 有什么区别?
前端·面试·typescript
朱涛的自习室2 小时前
70 美金的 Beyond Compare,我让 Gemini 4 和 DeepSeek 造个开源免费版(Day 2)
android·前端·人工智能
码艺-Alimjan3 小时前
Tauri 源码备份 · 打包 & 解压 总结,源码收集之路
前端·javascript·vue.js
小马不起床3 小时前
零基础玩转 FastAPI_11:前端后台管理页面
前端·状态模式·fastapi
子非鱼a4 小时前
【文件上传】一个普普通通的edu16
前端
美狐美颜SDK开放平台5 小时前
直播APP如何实现美白滤镜功能?视频美颜SDK技术开发方案详解
前端·数据库·人工智能·音视频·美颜sdk·直播美颜sdk·视频美颜sdk
pride.li6 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
屏什么录6 小时前
电脑如何录屏幕视频和声音?从工具到代码,7 种方案帮你彻底搞定
前端·音视频开发