一次调用就能处理所有 Fabric.js 导出图片的格式,还有 SVG 和 JSON。区域、倍数和背景都由你选。当前的缩放和平移不影响结果,因为导出总是使用文档坐标。
正在加载在线示例,代码在下方。
"use client";
import { Textbox } from "fabric";
import { downloadExport, type ExportFormat } from "fabricjs-document-engine";
import { useDocumentEngine } from "fabricjs-document-engine/react";
import { BracesIcon, FileCodeIcon, ImageIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import {
DemoCanvas,
DemoFrame,
seedShapes,
Status,
Toolbar,
ToolButton,
} from "./demo-ui";
import { useFabricCanvas } from "./use-fabric-canvas";
const FORMATS = [
{ format: "png", icon: ImageIcon },
{ format: "svg", icon: FileCodeIcon },
{ format: "json", icon: BracesIcon },
] as const;
export default function ExportDemo() {
const t = useTranslations("demos");
const { elementRef, canvas } = useFabricCanvas();
const engine = useDocumentEngine(canvas);
const [message, setMessage] = useState("");
const title = t("export.title");
// A small drawing to export. Text is measured once its font has loaded.
useEffect(() => {
if (!engine) return;
document.fonts.load("600 32px Yellix").finally(() => {
engine.canvas.add(
...seedShapes(),
new Textbox(title, {
originX: "left",
originY: "top",
left: 48,
top: 108,
width: 176,
fontFamily: "Yellix",
fontSize: 26,
fontWeight: 600,
textAlign: "center",
fill: "#0d0d0d",
})
);
engine.clearHistory();
});
}, [engine, title]);
async function download(format: ExportFormat) {
if (!engine) return;
try {
const result = await engine.export({ format, scale: 2 });
downloadExport(result, `drawing.${format}`);
setMessage(
t("export.done", {
width: result.width,
height: result.height,
format: format.toUpperCase(),
})
);
} catch (error) {
setMessage(t("export.failed", { message: String(error) }));
}
}
return (
<DemoFrame>
<DemoCanvas elementRef={elementRef} label={t("canvas")} />
<Toolbar>
{FORMATS.map(({ format, icon }) => (
<ToolButton
key={format}
icon={icon}
label={format.toUpperCase()}
disabled={!engine}
onClick={() => void download(format)}
/>
))}
</Toolbar>
<Status tone={message ? "ok" : "idle"}>
{message || t("export.hint")}
</Status>
</DemoFrame>
);
}导出并下载
import { downloadExport } from "fabricjs-document-engine";
const result = await engine.export({ format: "png", scale: 2 });
downloadExport(result, "poster.png");result 是 { format, mimeType, blob, width, height, warnings }。你可以上传 blob、放进 <img> 显示,或用 downloadExport 下载。
配置项
| 配置项 | 取值 | 默认值 |
|---|---|---|
format | 'png'、'jpeg'、'webp'、'svg' 或 'json' | 必填 |
scale | 输出尺寸倍数,比如 Retina 屏用 2 | 1 |
quality | 0 到 1,用于 JPEG 和 WebP | 0.92 |
area | 'canvas'、'content'、'selection' 或 { left, top, width, height } | 'canvas' |
padding | content 或 selection 周围的留白 | 0 |
background | 'keep'、'transparent' 或任意 CSS 颜色 | 'keep' |
signal | 用于取消的 AbortSignal | — |
'content'会裁剪到对象范围,做缩略图很方便。- JPEG 没有透明度,所以空白或透明的背景会变成白色,而不是黑色。
- 导出不会改变画布、撤销历史或未保存状态。
导出 SVG
const svg = await engine.export({
format: "svg",
area: "content",
padding: 20,
});导出 SVG 时,引擎会转义所有文字,所以文本框里的 <script> 仍然只是文字。
导出 JSON
const { document } = await engine.export({ format: "json" });导出 JSON 得到的,和保存时生成的可移植文档完全相同。设置了 assets.upload 时,只存在于当前标签页的图片会先上传,所以文件在任何设备上都能打开。
导出前检查
画布被污染(tainted canvas)是 Fabric.js 导出图片失败的经典原因。渲染之前,引擎会检查每个对象:
MISSING_IMAGE:图片没有加载成功。CROSS_ORIGIN_IMAGE:来自其他网站、没开 CORS 的图片会让浏览器拦截 PNG、JPEG 或 WebP 导出。SVG 和 JSON 不受影响。MISSING_FONT:字体不可用,且开启了assets.requireFonts。
发现问题时,export 会以 EXPORT_BLOCKED 拒绝,error.problems 列出每个 { code, message, url, objectIds }。先运行同样的检查,就能在菜单里提示用户:
const check = await engine.preflightExport({ format: "png" });
if (!check.ok) showProblems(check.problems);每种问题的修复方法见图片和字体。这个包不提供 PDF 导出;可以先用 Fabric.js 导出图片,再放进你选的 PDF 库里。