直接用 fabricjs导出svg 有两个问题。沿路径排列的文字位置不对;文件通过 URL 链接图片,链接一失效就只剩空框。引擎的 SVG 导出解决了这两点,其他对象仍然和 Fabric 写出来的完全一样。和其他 canvas导出svg 的做法相比,它不需要额外处理。
正在加载在线示例,代码在下方。
"use client";
import { FabricText, Path } from "fabric";
import { downloadExport } from "fabricjs-document-engine";
import { useDocumentEngine } from "fabricjs-document-engine/react";
import { DownloadIcon, EyeIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import {
COLORS,
DemoCanvas,
DemoFrame,
Status,
Toolbar,
ToolButton,
} from "./demo-ui";
import { PAGE, useFabricCanvas } from "./use-fabric-canvas";
type Output = "vector" | "fabric";
export default function SvgExportDemo() {
const t = useTranslations("demos");
const { elementRef, canvas } = useFabricCanvas();
const engine = useDocumentEngine(canvas);
const [preview, setPreview] = useState<{
url: string;
output: Output;
} | null>(null);
const [message, setMessage] = useState("");
const words = t("svg.words");
// Text that follows a curve, centred on it, with a highlight behind it.
useEffect(() => {
if (!engine) return;
engine.canvas.add(
new FabricText(words, {
path: new Path("M 40 230 Q 240 20 440 230", {
fill: "",
stroke: "#d4d4d8",
strokeWidth: 2,
strokeDashArray: [6, 6],
}),
left: PAGE.width / 2,
top: PAGE.height / 2,
fontFamily: "sans-serif",
fontSize: 28,
fill: "#0d0d0d",
pathAlign: "center",
textAlign: "center",
textBackgroundColor: COLORS[4],
underline: true,
})
);
engine.clearHistory();
}, [engine, words]);
// The preview URL belongs to this component; free it when it changes.
useEffect(
() => () => {
if (preview) URL.revokeObjectURL(preview.url);
},
[preview]
);
async function render(output: Output, download: boolean) {
if (!engine) return;
const result = await engine.export({
format: "svg",
svg: { textOnPath: output, embedImages: true },
});
if (download) {
downloadExport(result, "curved-text.svg");
setMessage(t("svg.downloaded"));
return;
}
setPreview({ url: URL.createObjectURL(result.blob), output });
setMessage(
t(output === "vector" ? "svg.previewVector" : "svg.previewFabric")
);
}
return (
<DemoFrame>
<DemoCanvas elementRef={elementRef} label={t("canvas")} />
{preview && (
<figure className="overflow-hidden rounded-lg border bg-white">
{/* eslint-disable-next-line @next/next/no-img-element -- a blob URL of the export itself */}
<img
src={preview.url}
alt={t("svg.previewAlt")}
className="block w-full"
/>
</figure>
)}
<Toolbar>
<ToolButton
icon={DownloadIcon}
label={t("svg.download")}
primary
disabled={!engine}
onClick={() => void render("vector", true)}
/>
<ToolButton
icon={EyeIcon}
label={t("svg.showVector")}
disabled={!engine}
onClick={() => void render("vector", false)}
/>
<ToolButton
icon={EyeIcon}
label={t("svg.showFabric")}
disabled={!engine}
onClick={() => void render("fabric", false)}
/>
</Toolbar>
<Status tone={message ? "ok" : "idle"}>{message || t("svg.hint")}</Status>
</DemoFrame>
);
}曲线文字
Fabric.js 的 SVG 里没有 <textPath>,svg 文字路径是用一个个旋转的字母写出来的。这种输出会忽略 pathAlign,把抬高的字母移向错误的方向,还把文字背景和下划线画成直的。文字里有空格时,Fabric 6 和 7 甚至会写出无效的 XML,浏览器和 Illustrator 都打不开。
const result = await engine.export({ format: "svg" });现在每次 SVG 导出都会把曲线文字的每个字母写在画布绘制它的位置,背景和装饰线也在同一个位置:
- 沿路径的
pathAlign、pathSide、pathStartOffset和textAlign - 单个字母的样式、抬高和降低的字母(
deltaY)、描边和paintFirst - 文字背景、下划线、上划线和删除线
- 可见时,路径本身
每个字母都是带变换的普通 <text>,所有 SVG 阅读器都能理解,文字也仍然可以编辑。传入 svg: { textOnPath: "fabric" } 可以保留 Fabric 自己的输出。
嵌入图片和字体
const result = await engine.export({
format: "svg",
svg: {
embedImages: true,
embedFonts: { "Brand Sans": "/fonts/brand-sans.woff2" },
},
});embedImages: true会嵌入图片,把每张图片作为数据放进文件,包括图案填充和编组里的图片。来自其他网站、没有 CORS 的图片会保留为链接,并给出带对象 id 的IMAGE_NOT_EMBEDDED警告。- 图片无法嵌入时,
embedImages: "require"会以EXPORT_BLOCKED阻止导出。 maxEmbeddedImageBytes会略过超过限制的图片(默认 25 MB)。embedFonts把字体族对应到一个文件:URL 或文件字节。设置在单个字母上的字体也会被嵌入,而 Fabric 自己的@font-face输出会漏掉它们,导致换一台电脑打开时字体变形。
按需求选择配置项
每次 fabricjs导出svg 都可以用下面的配置项,按文件的去处来选:
| 需求 | 配置项 |
|---|---|
| 和画布看起来一样的 SVG | 默认值 |
| 给 Illustrator 或其他设备用的文件 | embedImages: true |
| 在任何电脑上都显示品牌字体 | embedFonts |
| 用于对比的 Fabric 原始输出 | textOnPath: "fabric" |
编组里有沿路径排列的文字时,给编组设置 objectCaching: false。开启缓存时,Fabric 会在画布上裁掉高出路径的字母,而 SVG 会完整显示它们。更多导出格式见导出,PDF 导出也用了同样的曲线文字修正。