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
相关产品推荐
相关产品推荐

