fabric-js

star 2

[Applies to: **/*.{js,jsx}] This guide outlines definitive best practices for using fabric-js (v6.9.0+) in JavaScript/TypeScript projects, focusing on performance, maintainability, and modern API usage.

Tryboy869 By Tryboy869 schedule Updated 2/28/2026

name: fabric-js description: "[Applies to: **/*.{js,jsx}] This guide outlines definitive best practices for using fabric-js (v6.9.0+) in JavaScript/TypeScript projects, focusing on performance, maintainability, and modern API usage." source: "cursor_mdc"

fabric-js Best Practices

This document provides definitive guidelines for developing with fabric-js (v6.9.0+), emphasizing performance, maintainability, and modern API adoption. Adhere to these rules to ensure robust and efficient canvas applications.

1. Code Organization and Structure

1.1. Embrace TypeScript and ES Modules

Always use TypeScript for type safety and leverage ES6 modules for efficient bundling. Import only the specific fabric components you need.

❌ BAD: Global fabric access, full library import

// main.js
import 'fabric'; // Imports everything
const canvas = new fabric.Canvas('myCanvas');
canvas.add(new fabric.Rect({ left: 10, top: 10, width: 50, height: 50 }));

✅ GOOD: Modular, typed imports

// main.ts
import { Canvas, Rect, IText } from 'fabric';

const canvas = new Canvas('myCanvas');
const rect = new Rect({ left: 10, top: 10, width: 50, height: 50, fill: 'red' });
canvas.add(rect);

1.2. Choose the Right Canvas Type

Use StaticCanvas for read-only displays or high-frame-rate scenes where interactivity is not required. It offers a significant performance boost by skipping event handling.

❌ BAD: Using Canvas when no interaction is needed

// For displaying a static image or animation without user interaction
const canvas = new Canvas('myStaticCanvas');
canvas.add(someFabricObject);
canvas.renderAll();

✅ GOOD: Using StaticCanvas for static or high-performance rendering

// For displaying a static image or animation without user interaction
import { StaticCanvas, Rect } from 'fabric';

const staticCanvas = new StaticCanvas('myStaticCanvas');
const rect = new Rect({ left: 10, top: 10, width: 50, height: 50, fill: 'blue' });
staticCanvas.add(rect);
staticCanvas.renderAll();

2. Performance Considerations

2.1. Disable Unnecessary Interactivity

Minimize overhead by disabling selection, controls, and borders on objects or the canvas itself when not needed.

❌ BAD: Default interactive objects everywhere

const rect = new Rect({ left: 10, top: 10, width: 50, height: 50, fill: 'green' });
canvas.add(rect); // Selectable, has controls/borders by default

✅ GOOD: Optimize object interactivity

const rect = new Rect({
  left: 10, top: 10, width: 50, height: 50, fill: 'green',
  selectable: false,       // No selection
  hasControls: false,      // No scaling/rotation controls
  hasBorders: false,       // No bounding box borders
  hasRotatingPoint: false, // No rotating point
});
canvas.add(rect);

// Disable canvas-wide selection if not needed
canvas.selection = false;

2.2. Leverage Object Caching

Enable objectCaching for complex objects (e.g., paths, filtered images, groups) to pre-rasterize them, avoiding repeated expensive drawing operations.

❌ BAD: Complex object without caching

import { Path } from 'fabric';
const complexPath = new Path('M 0 0 L 100 0 L 50 100 z', { fill: 'purple' });
canvas.add(complexPath); // Rerendered every time

✅ GOOD: Enable object caching for performance

import { Path } from 'fabric';
const complexPath = new Path('M 0 0 L 100 0 L 50 100 z', {
  fill: 'purple',
  objectCaching: true, // Cache complex path as a bitmap
});
canvas.add(complexPath);

2.3. Optimize Batch Operations

When adding or removing many objects, disable renderOnAddRemove to prevent unnecessary re-renders for each operation. Manually call canvas.renderAll() once after all changes.

❌ BAD: Multiple renders for batch operations

for (let i = 0; i < 100; i++) {
  canvas.add(new Rect({ left: i * 2, top: i * 2, width: 10, height: 10 }));
} // canvas.renderAll() called 100 times

✅ GOOD: Batch operations with single render

canvas.renderOnAddRemove = false; // Disable auto-render
for (let i = 0; i < 100; i++) {
  canvas.add(new Rect({ left: i * 2, top: i * 2, width: 10, height: 10 }));
}
canvas.renderOnAddRemove = true; // Re-enable if needed later
canvas.renderAll(); // Render once after all additions

2.4. Manage Object Visibility

Set object.visible = false for objects outside the current viewport or not intended to be seen, preventing them from being drawn.

const offScreenRect = new Rect({ left: 1000, top: 1000, width: 50, height: 50 });
offScreenRect.visible = false; // Prevents rendering when off-screen
canvas.add(offScreenRect);

3. Common Patterns and Anti-patterns

3.1. Use Modern Accessors

Always use getScaledWidth() and getScaledHeight() to retrieve an object's dimensions, as getWidth() and getHeight() are deprecated.

❌ BAD: Using deprecated accessors

const rect = new Rect({ width: 100, height: 50, scaleX: 2 });
console.log(rect.getWidth()); // Throws error in v2+

✅ GOOD: Using modern accessors

const rect = new Rect({ width: 100, height: 50, scaleX: 2 });
console.log(rect.getScaledWidth());  // Outputs 200
console.log(rect.getScaledHeight()); // Outputs 50

3.2. Prefer fabric.Group over fabric.PathGroup

fabric.PathGroup is obsolete. Use fabric.Group for all object grouping, including SVG imports.

❌ BAD: Using fabric.PathGroup

// This class no longer exists in modern Fabric.js
const pathGroup = new fabric.PathGroup([path1, path2]);

✅ GOOD: Using fabric.Group

import { Group, Rect, Circle } from 'fabric';
const rect = new Rect({ width: 50, height: 50, fill: 'red' });
const circle = new Circle({ radius: 25, fill: 'blue' });
const group = new Group([rect, circle], { left: 100, top: 100 });
canvas.add(group);

3.3. Correct Image Filter Application

applyFilters is now synchronous. Call canvas.requestRenderAll() (or renderAll()) after applying filters to update the canvas.

❌ BAD: Old async filter syntax

// This will throw an error
image.applyFilters(canvas.renderAll.bind(canvas));

✅ GOOD: Modern sync filter application

import { FabricImage, Image } from 'fabric';

FabricImage.fromURL('path/to/image.png', (img) => {
  img.filters.push(new Image.filters.Grayscale());
  img.applyFilters(); // Synchronous
  canvas.add(img);
  canvas.requestRenderAll(); // Request a render after filters are applied
});

3.4. Serialize Canvas State Reliably

Always use canvas.toJSON() for serialization. If you have custom properties, ensure they are included in toObject methods of your custom classes. Version your JSON schema for future compatibility.

// Save canvas state
const jsonState = canvas.toJSON(['customProperty1', 'customProperty2']);
localStorage.setItem('canvasState', JSON.stringify(jsonState));

// Load canvas state
const savedState = JSON.parse(localStorage.getItem('canvasState'));
canvas.loadFromJSON(savedState, () => {
  canvas.renderAll();
  console.log('Canvas loaded from JSON');
});

4. Testing Approaches

4.1. Benchmark Rendering Performance

Regularly benchmark canvas.renderAll() to identify performance regressions, especially after significant changes or adding complex objects.

console.time('renderDuration');
canvas.renderAll();
console.timeEnd('renderDuration'); // Log render time

4.2. Test on Both Canvas Types

If your application uses both Canvas and StaticCanvas (e.g., for editing vs. display modes), ensure all features and objects render correctly on both.

Install via CLI
npx skills add https://github.com/Tryboy869/dojutsu-for-ai --skill fabric-js
Repository Details
star Stars 2
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator