You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native Expo中react-native-canvas自定义字体不生效问题求助

问题:react-native-canvas无法加载Expo预加载的自定义字体

在React Native + Expo项目中,通过@expo-google-fonts/pacifico安装并在_layout.tsx中加载了Pacifico字体,App内原生文本组件能正常显示该字体,但使用react-native-canvas绘制文本时,自定义字体不生效,自动 fallback 到系统默认字体。


核心原因

react-native-canvas基于WebView实现,其运行环境与React Native原生环境相互隔离。Expo的useFonts仅在原生环境中加载字体,WebView无法直接访问这些字体资源,必须手动将字体注入到canvas的WebView上下文。


解决步骤

1. 将字体转换为Base64编码

无论是Expo Google Fonts还是本地字体文件,都需要先转换为Base64格式,才能在WebView中加载:

import * as FileSystem from 'expo-file-system';
import { Pacifico_400Regular } from '@expo-google-fonts/pacifico';

// 封装字体转Base64函数
async function getFontBase64(fontUri: string) {
  const base64 = await FileSystem.readAsStringAsync(fontUri, {
    encoding: FileSystem.EncodingType.Base64,
  });
  return `data:font/truetype;base64,${base64}`;
}

2. 在canvas中注册并加载字体

在绘制文本前,通过WebView的FontFaceAPI注册字体,等待加载完成后再执行绘制逻辑:
修改后的drawOnCanvas核心代码:

const drawOnCanvas = useCallback(
  async (layers: TextLayer[]) => {
    if (!canvasRef.current || !imageUrl || !processedImageurl) return;

    try {
      const ctx = canvasRef.current.getContext("2d");
      const img = new CanvasImage(canvasRef.current);
      const img_2 = new CanvasImage(canvasRef.current);

      canvasRef.current.width = screenWidth;
      canvasRef.current.height = canvasHeight;

      // 注册并加载Pacifico字体
      const pacificoBase64 = await getFontBase64(Pacifico_400Regular);
      const fontFace = new (canvasRef.current as any).FontFace(
        'Pacifico', // 字体名称必须与使用时一致
        `url(${pacificoBase64})`
      );
      await fontFace.load();
      (canvasRef.current as any).fonts.add(fontFace);

      // 原图片加载逻辑...
      const [originalBase64, processedBase64] = await Promise.all([
        FileSystem.readAsStringAsync(imageUrl, { encoding: FileSystem.EncodingType.Base64 }),
        FileSystem.readAsStringAsync(processedImageurl, { encoding: FileSystem.EncodingType.Base64 }),
      ]);
      const imgLoaded = new Promise(resolve => img.addEventListener("load", resolve));
      const img2Loaded = new Promise(resolve => img_2.addEventListener("load", resolve));
      img.src = `data:image/png;base64,${processedBase64}`;
      img_2.src = `data:image/png;base64,${originalBase64}`;
      await Promise.all([imgLoaded, img2Loaded]);

      // 原图片绘制逻辑...
      const ratio = Math.min(screenWidth / img_2.width, canvasHeight / img_2.height);
      const displayWidth = img_2.width * ratio;
      const displayHeight = img_2.height * ratio;
      ctx.clearRect(0, 0, screenWidth, canvasHeight);
      ctx.drawImage(img_2, (screenWidth - displayWidth)/2, (canvasHeight - displayHeight)/2, displayWidth, displayHeight);

      // 绘制文本层(此时字体已加载完成)
      layers.forEach((layer) => {
        ctx.save();
        ctx.translate(layer.x, layer.y);
        ctx.rotate((layer.rotation * Math.PI) / 180);
        ctx.font = `${layer.fontWeight} ${layer.fontSize}px "Pacifico"`;
        ctx.fillStyle = layer.color;
        ctx.globalAlpha = layer.opacity;
        ctx.fillText(layer.text, 0, 0);
        ctx.restore();
      });

      // 原后续绘制逻辑...
      ctx.drawImage(img, (screenWidth - displayWidth)/2, (canvasHeight - displayHeight)/2, displayWidth, displayHeight);
    } catch (error) {
      console.error("Error drawing image:", error);
    }
  },
  [imageUrl, processedImageurl, screenWidth, canvasHeight]
);

3. 多字体批量加载方案

如果需要支持多个自定义字体,可以封装批量加载函数:

async function loadAllCustomFonts() {
  const canvas = canvasRef.current;
  if (!canvas) return;

  const fontList = [
    { name: 'Pacifico', uri: Pacifico_400Regular },
    { name: 'Rubik-Bold', uri: require('../assets/fonts/Rubik-Bold.ttf') },
    { name: 'Rubik-Regular', uri: require('../assets/fonts/Rubik-Regular.ttf') },
    // 其他字体...
  ];

  const loadPromises = fontList.map(async ({ name, uri }) => {
    const base64 = await FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.Base64 });
    const fontFace = new (canvas as any).FontFace(name, `url(data:font/truetype;base64,${base64})`);
    await fontFace.load();
    (canvas as any).fonts.add(fontFace);
  });

  await Promise.all(loadPromises);
}

在drawOnCanvas开头调用await loadAllCustomFonts()即可。


关键注意事项

  • 字体注册名称必须与绘制时ctx.font中的名称完全一致(包括大小写)
  • 必须等待fontFace.load()完成后再绘制文本,否则会 fallback 到默认字体
  • iOS平台部分字体需确认MIME类型(可尝试font/opentype替代font/truetype)
  • 若字体加载失败,检查Base64编码是否正确,或字体文件是否损坏

内容的提问来源于stack exchange,提问作者Raghunadh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 19:16:06