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

如何通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 01:24:04