如何通过opentype.js、fabric与Promises实现文本转路径异步导出SVG
Fabric 画布文本转路径异步实现方案
原代码核心缺陷有两个:一是没有等待Fabric.Canvas.loadFromJSON的异步加载完成就读取对象列表,二是没有收集文本转路径的异步任务就直接返回画布。以下是完整可运行的实现:
// 可选:字体缓存,避免重复加载同一字体 const fontCache = {}; const loadFont = (fontUrl) => { if (fontCache[fontUrl]) return Promise.resolve(fontCache[fontUrl]); return opentype.load(fontUrl).then(font => { fontCache[fontUrl] = font; return font; }); }; const toPath = async () => { // 1. 包装loadFromJSON为Promise,等待画布完整加载 const myCanvas = new fabric.Canvas("render-canvas"); await new Promise(resolve => { myCanvas.loadFromJSON(canvas.toJSON(), resolve); }); const objects = myCanvas.getObjects(); const convertTasks = []; // 2. 遍历所有对象,生成每个文本的转换任务 for (let index = 0; index < objects.length; index++) { const object = objects[index]; // 跳过非文本、无字体的元素 if (object.type !== "i-text" || !object.fontFamily) continue; const fontUrl = process.env.PUBLIC_URL + "/fonts/ttf/" + getFontByName(object.fontFamily)["url"]; const task = loadFont(fontUrl).then(font => { // 生成OpenType路径 const path = font.getPath(object.text, 0, 0, object.fontSize); // 拷贝原文本全量属性到路径对象 const fabricPath = new fabric.Path(path.toPathData(20), { left: object.left, top: object.top, fill: object.fill, stroke: object.stroke, strokeWidth: object.strokeWidth, opacity: object.opacity, angle: object.angle, scaleX: object.scaleX, scaleY: object.scaleY, skewX: object.skewX, skewY: object.skewY, originX: object.originX, originY: object.originY, }); // 返回替换元信息,不直接修改画布 return { index, targetObject: object, path: fabricPath }; }).catch(err => { console.error(`字体加载失败,跳过当前文本:`, err); return null; // 失败项返回null,后续统一过滤 }); convertTasks.push(task); } // 3. 等待所有字体加载、路径生成完成 const replaceResults = await Promise.all(convertTasks); // 4. 统一替换对象列表,避免中途修改数组导致索引错乱 const newObjectList = [...myCanvas.getObjects()]; replaceResults.forEach(item => { if (!item) return; newObjectList[item.index] = item.path; }); myCanvas.setObjects(newObjectList); myCanvas.requestRenderAll(); // 所有操作完成后返回画布实例 return myCanvas; };
关键改动说明
- 包装
loadFromJSON为Promise:Fabric 加载序列化JSON是异步操作,必须等待加载完成后才能拿到完整的对象列表,避免漏取元素 - 替换
forEach为for循环:能准确捕获每个对象的原始索引,为后续统一替换做准备 - 收集所有异步转换任务:为每个文本元素生成独立的转换Promise,确保不会被事件循环跳过
- 增加字体缓存:同一字体重复加载时直接从缓存读取,减少不必要的网络请求
- 全量属性拷贝:将原文本的位置、变换、样式参数全部同步到生成的
fabric.Path对象,保证视觉一致 - 统一替换逻辑:所有路径生成完成后再一次性替换画布对象列表,避免中途修改数组导致的索引错乱,元素顺序和原画布完全一致
- 细粒度错误处理:单个字体加载失败时不会中断整个转换流程,可按需调整为抛出错误终止流程
调用方式(完全匹配预期)
toPath().then(canvas => { const svgContent = canvas.toSVG(); // 此处可处理最终SVG内容,比如导出、下载等 });
适配更多文本类型:如果画布中存在
text、textbox等其他文本元素,可将类型判断条件修改为["i-text", "text", "textbox"].includes(object.type),覆盖所有文本场景。
内容的提问来源于stack exchange,提问作者user6329530
相关产品推荐
相关产品推荐

