Fabric.js Svelte 组件要在 onMount 里创建画布,因为那时 <canvas> 元素才存在。createDocumentStateStore 本身就有 subscribe 方法,所以把它变成 Svelte store 只要一行。
组件
<script lang="ts">
import { Canvas, Rect } from "fabric";
import {
createDocumentEngine,
createDocumentStateStore,
type DocumentEngine,
} from "fabricjs-document-engine";
import { onMount } from "svelte";
import { readable, type Readable } from "svelte/store";
let element: HTMLCanvasElement;
let engine: DocumentEngine | undefined;
let state: Readable<ReturnType<ReturnType<typeof createDocumentStateStore>["getSnapshot"]>> | undefined;
onMount(() => {
const canvas = new Canvas(element, { width: 800, height: 500 });
engine = createDocumentEngine({ canvas });
const store = createDocumentStateStore(engine);
state = readable(store.getSnapshot(), (set) =>
store.subscribe(() => set(store.getSnapshot()))
);
return () => {
store.destroy();
engine?.destroy();
canvas.dispose();
};
});
function addSquare() {
engine?.canvas.add(new Rect({ width: 80, height: 60, fill: "#e9c46a" }));
}
</script>
<div role="toolbar">
<button on:click={addSquare}>Square</button>
<button disabled={!$state?.canUndo} on:click={() => engine?.undo()}>
Undo {$state?.undoLabel ?? ""}
</button>
<button disabled={!$state?.canRedo} on:click={() => engine?.redo()}>Redo</button>
</div>
<canvas bind:this={element} aria-label="Drawing canvas"></canvas>store 怎么工作
readable(initial, start) 会在第一个组件订阅时调用 start,在最后一个组件离开时调用它返回的函数。store.subscribe 返回的正好就是取消订阅的函数,所以 Svelte store 和引擎的 store 不用额外代码就能保持同步。
SvelteKit
Fabric.js 需要浏览器。在 SvelteKit 里,onMount 只在浏览器中运行,所以上面的 Fabric.js Svelte 代码在服务端渲染时是安全的。如果你在共享模块顶部导入了 Fabric.js,请把这个导入移进组件里。
要保存,请给 createDocumentEngine 加上存储适配器。原生 JavaScript 指南展示了不用框架时同样的引擎调用。