fabricjs/fabric.js · error · FabricError
Trying to initialize a canvas that has already been initiali
Error message
Trying to initialize a canvas that has already been initialized. Did you forget to dispose the canvas?
What it means
Fabric.js marks the canvas element it initializes with a `data-fabric="main"` attribute so a canvas is never initialized twice. When you construct a `StaticCanvas`/`FabricCanvas` over an element that already carries this attribute (from a previous, undisposed instance), this error is thrown to prevent corrupting internal state and leaking listeners.
Source
Thrown at packages/core/src/canvas/DOMManagers/StaticCanvasDOMManager.ts:37
*/
private _originalCanvasStyle?: string;
lower: CanvasItem;
constructor(arg0?: string | HTMLCanvasElement) {
const el = this.createLowerCanvas(arg0);
this.lower = { el, ctx: el.getContext('2d')! };
}
protected createLowerCanvas(arg0?: HTMLCanvasElement | string) {
// canvasEl === 'HTMLCanvasElement' does not work on jsdom/node
const el = isHTMLCanvas(arg0)
? arg0
: (arg0 &&
(getFabricDocument().getElementById(arg0) as HTMLCanvasElement)) ||
createCanvasElement();
if (el.hasAttribute('data-fabric')) {
throw new FabricError(
'Trying to initialize a canvas that has already been initialized. Did you forget to dispose the canvas?',
);
}
this._originalCanvasStyle = el.style.cssText;
el.setAttribute('data-fabric', 'main');
el.classList.add('lower-canvas');
return el;
}
cleanupDOM({ width, height }: TSize) {
const { el } = this.lower;
// restore canvas style and attributes
el.classList.remove('lower-canvas');
el.removeAttribute('data-fabric');
// restore canvas size to original size in case retina scaling was applied
el.setAttribute('width', `${width}`);
el.setAttribute('height', `${height}`);
el.style.cssText = this._originalCanvasStyle || '';View on GitHub (pinned to 2bd4992cab)
Solutions
- Dispose the previous instance before re-initializing: keep a ref and call `canvas.dispose()` in cleanup (e.g. React useEffect return, Vue onBeforeUnmount).
- In React StrictMode/dev double-mount scenarios, make the effect idempotent: dispose on every cleanup run, not just unmount.
- If the element is stale and you truly want a fresh canvas, remove the attribute first: `el.removeAttribute('data-fabric')` (only when you know no live instance owns it), or render a brand-new <canvas> element.
- Initialize from a fresh element or id that isn't already managed by Fabric.
Example fix
// before (React)
useEffect(() => {
const canvas = new fabric.Canvas(ref.current); // throws on StrictMode remount
}, []);
// after
useEffect(() => {
const canvas = new fabric.Canvas(ref.current);
return () => { canvas.dispose(); };
}, []); Defensive patterns
Strategy: validation
Validate before calling
const el = document.getElementById('c') as HTMLCanvasElement;
if (el.hasAttribute('data-fabric')) {
// previous instance still owns this element
previousCanvas?.dispose();
el.removeAttribute('data-fabric'); // only if no live instance remains
}
const canvas = new fabric.Canvas(el); Type guard
const isFabricManaged = (el: HTMLCanvasElement): boolean =>
el.hasAttribute('data-fabric'); Try / catch
try {
canvas = new fabric.Canvas(el);
} catch (e) {
if (e instanceof Error && e.message.includes('already been initialized')) {
await oldInstance?.dispose();
canvas = new fabric.Canvas(el); // retry on the freed element
} else throw e;
} Prevention
- Always dispose canvas instances in framework cleanup hooks (useEffect return, onBeforeUnmount).
- Store the canvas instance in a ref/variable so dispose is always reachable.
- In dev StrictMode, accept the double mount and dispose in every cleanup run.
- Prefer rendering a fresh <canvas> element per mount instead of reusing one by id.
When it happens
Trigger: Creating `new fabric.Canvas(existingEl)` twice on the same DOM canvas without calling `dispose()` in between; re-initializing after a hot-module reload or framework re-render (React/Vue) that reuses the same canvas element; re-mounting a component that queries the canvas by id while the old instance was never disposed.
Common situations: React StrictMode double-mounting in development; forgetting `canvas.dispose()` in a cleanup hook; HMR re-running initialization code against the same element; SSR/hydration reusing the server-rendered canvas element that a client instance already claimed.
Related errors
- No class registered for ${classType}
- Fabric env was not initialized. Import fabric, fabric/node,
- Vertex, fragment shader or program creation error
- Vertex shader compile error for ${this.type}: ${gl.getShader
- Fragment shader compile error for ${this.type}: ${gl.getShad
AI-assisted analysis of fabricjs/fabric.js@2bd4992cab (2026-08-28).
Data as JSON: /api/errors/dfc588eedb445949.
Report an issue: GitHub.