A modern, powerful browser-based image editor built with FabricJS and TypeScript. This library provides a complete image editing solution with professional features for web applications.
- Montage Area - Dedicated workspace with clipping region for precise cropping
- Multi-layer Support - Layer management with ordering, visibility, and locking
- History System - Full undo/redo with state management
- Rich Text Editing - Text manager with typography controls, uppercase transforms, and history-aware updates
- Object Transformations - Zoom, rotate, flip, fit, and scale operations
- Professional Tools - Copy/paste, grouping, selection, and alignment
- Background Management - Color, multi-stop gradient, and image backgrounds
- Image Import/Export - Support for PNG, JPG, SVG, and PDF formats
- Precision Alignment - Snapping to montage edges/centers and nearby objects with visual guides and spacing detection
- Live Measurements - Hold
Altwith a selection to display distance guides to the hovered object or montage area - Web Worker Integration - Heavy operations run in background threads
- Font Loader - FontManager handles Google Fonts + custom sources with automatic
@font-faceregistration - Configurable Toolbar - Dynamic toolbar with context-sensitive actions
- Clipboard Integration - Native copy/paste support with system clipboard
- TypeScript Support - Full type definitions included
- Modular Architecture - Clean separation of concerns with manager classes
- Event System - Rich event handling for integration
- Responsive Design - Adapts to different screen sizes and containers
- Testing Infrastructure - Jest test suite with 45%+ coverage
- Web Worker Support - Background processing for heavy operations
npm install @anu3ev/fabric-image-editorRequirements:
- Node.js ≥ 20.0.0
- NPM ≥ 9.0.0
- Modern browser with ES2016+ support
Create a container in your HTML and initialize the editor:
<div id="editor"></div>import initEditor from '@anu3ev/fabric-image-editor'
document.addEventListener('DOMContentLoaded', async () => {
const editor = await initEditor('editor', {
montageAreaWidth: 512,
montageAreaHeight: 512,
editorContainerWidth: '100%',
editorContainerHeight: '100vh'
})
// The editor is now ready to use!
console.log('Editor initialized:', editor)
})// Import an image
await editor.imageManager.importImage({
source: 'path/to/image.jpg',
scale: 'image-contain' // 'image-contain', 'image-cover', 'scale-montage'
})
// Export the canvas
const result = await editor.imageManager.exportCanvasAsImageFile({
fileName: 'edited-image.png',
contentType: 'image/png' // Supports: 'image/png', 'image/jpeg', 'image/svg+xml', 'application/pdf'
})
// Handle the exported file (result.image is File, Blob, or Base64 string)
const url = URL.createObjectURL(result.image)
// Use the URL for download or display// Set a color background
editor.backgroundManager.setColorBackground({ color: '#ff0000' })
// Set a gradient background (supports multi-stop ramps)
editor.backgroundManager.setGradientBackground({
gradient: {
type: 'linear', // 'linear' or 'radial'
angle: 120,
colorStops: [
{ offset: 0, color: '#ff8a00' },
{ offset: 45, color: '#e52e71' },
{ offset: 100, color: '#4a00e0' }
]
},
customData: {
customProperty: 'value'
},
withoutSave: false
})
// Simple two-stop gradients still work:
// use startColor/endColor with optional startPosition/endPosition (0-100).
// For radial gradients pass centerX/centerY/radius in percentages.
// Set an image background
await editor.backgroundManager.setImageBackground({ imageSource: 'bg-image.jpg' })
// Remove background
editor.backgroundManager.removeBackground()Offsets in colorStops use percentages from 0 to 100 and are normalized for Fabric gradients.
// Add a text layer with custom style
const textbox = editor.textManager.addText({
text: 'Привет, Fabric!',
fontFamily: 'Merriweather',
fontSize: 64,
bold: true,
align: 'center',
color: '#1f2933'
})
// Update existing text
editor.textManager.updateText({
target: textbox,
style: {
text: 'HELLO FABRIC',
uppercase: true,
strokeColor: '#2563eb',
strokeWidth: 2
}
})
// Background styling with padding and rounded corners
const bgTextbox = editor.textManager.addText({
text: 'New text',
backgroundColor: '#ffffff',
backgroundOpacity: 0.85,
paddingTop: 24,
paddingRight: 32,
paddingBottom: 24,
paddingLeft: 32,
radiusTopLeft: 8,
radiusTopRight: 8,
radiusBottomRight: 8,
radiusBottomLeft: 8
})
editor.textManager.updateText({
target: bgTextbox,
style: {
paddingTop: 40,
paddingBottom: 40,
radiusTopLeft: 16,
radiusTopRight: 16
}
})ShapeManager works with composite objects: an outer shape plus an inner text node that stay in sync during layout, scaling, history restore, copy/paste, and template rehydration.
Built-in preset keys: circle, triangle, square, diamond, pentagon, hexagon, star, sparkle, heart, arrow-right-fat, arrow-up-fat, arrow-right, arrow-down-fat, arrow-up-down, arrow-left-right, drop, cross, gear, badge, bookmark, tag, moon.
// Create a shape group with text
const badge = await editor.shapeManager.add({
presetKey: 'badge',
options: {
id: 'promo-badge',
width: 220,
height: 160,
text: 'SALE',
fill: '#111827',
stroke: '#f59e0b',
strokeWidth: 6,
textStyle: {
fontSize: 34,
color: '#ffffff',
bold: true
},
alignH: 'center',
alignV: 'middle'
}
})
// Update shape-only styling
editor.shapeManager.setOpacity({
target: badge,
opacity: 0.9
})
editor.shapeManager.setStroke({
target: badge,
stroke: '#22c55e',
strokeWidth: 4,
dash: [12, 6]
})
// Update inner text with layout-aware recalculation
editor.shapeManager.updateTextStyle({
target: 'promo-badge',
style: {
text: 'LIMITED',
uppercase: true,
fontSize: 30,
color: '#f9fafb'
}
})
// Swap preset while preserving text, transforms, and metadata
await editor.shapeManager.update({
target: badge,
presetKey: 'tag',
options: {
fill: '#dc2626',
width: 240
}
})All mutating ShapeManager methods save to history by default. Pass withoutSave: true for internal or batched updates.
By default the editor ships with a curated Google Fonts collection (Latin + Cyrillic coverage).
If you want to use your own fonts, supply a fonts array – the provided list will replace the defaults.
import initEditor from '@anu3ev/fabric-image-editor'
await initEditor('editor', {
fonts: [
{
family: 'Alegreya Sans',
source: "url('https://fonts.gstatic.com/s/alegreyasans/v26/5aUz9_-1phKLFgshYDvh6Vwt7VptvQ.woff2') format('woff2')",
descriptors: {
style: 'normal',
weight: '400',
display: 'swap'
}
},
{
family: 'My Custom Font',
source: "url('https://example.com/fonts/my-font.woff2') format('woff2')",
descriptors: {
style: 'normal',
weight: '400',
display: 'swap',
unicodeRange: 'U+0000-00FF'
}
}
]
})ℹ️ Leave
fontsundefined to rely on the built-in defaults. Passing the property replaces that set with the fonts you specify.
The repository includes a comprehensive demo showcasing all features:
git clone https://github.com/Anu3ev/image-editor.git
cd image-editor
npm install
npm run devVisit the demo at: https://anu3ev.github.io/image-editor/
The editor follows a modular architecture with specialized managers:
ImageManager- Image import/export, format handling, PDF generationCanvasManager- Canvas sizing, scaling, and viewport managementHistoryManager- Undo/redo functionality with state persistenceTextManager- Text object creation, styling, uppercase handling, and history integrationLayerManager- Object layering, z-index management, send to back/frontBackgroundManager- Background colors, gradients, and imagesTransformManager- Object transformations, fitting, and scalingZoomManager- Zoom limits, fit calculations, and smooth viewport centering
SelectionManager- Object selection and multi-selection handlingClipboardManager- Copy/paste with system clipboard integrationGroupingManager- Object grouping and ungrouping operationsDeletionManager- Object deletion with group handlingShapeManager- Preset-based shape groups with inner text, layout, scaling, and style controlsObjectLockManager- Object locking and unlocking functionalitySnappingManager- Alignment guides and equal-spacing snaps while moving objectsMeasurementManager- ALT-triggered distance guides to hovered objects or the montage areaPanConstraintManager- Constrains canvas panning relative to zoom and montage boundsWorkerManager- Web Worker integration for heavy operationsFontManager- Font loading via FontFace API or fallback @font-face injectionModuleLoader- Dynamic module loading (jsPDF, etc.)ErrorManager- Error handling and user notificationsTemplateManager(src/editor/template-manager/index.ts) - Serializes and reapplies object/group templates with optional background preservation
ToolbarManager- Dynamic toolbar with configurable actionsCustomizedControls- Custom FabricJS controls and interactionsInteractionBlocker- UI blocking during operationsAngleIndicatorManager- Rotation angle badge shown while rotating selected objects (toggle viashowRotationAngle)
initEditor(containerId, options): Promise<ImageEditor>Parameters:
containerId(string) - HTML container element IDoptions(CanvasOptions) - Configuration object
Common Options:
{
// Canvas dimensions (internal resolution)
montageAreaWidth: 512,
montageAreaHeight: 512,
// Container dimensions (display size)
editorContainerWidth: '800px',
editorContainerHeight: '600px',
// Initial image
initialImage: {
source: 'path/to/image.jpg',
scale: 'image-contain'
},
// Content types for import
acceptContentTypes: ['image/png', 'image/jpeg', 'image/svg+xml'],
// Callback when ready
_onReadyCallback: (editor) => console.log('Ready!')
}// Import image from file or URL
await editor.imageManager.importImage({
source: File | string,
scale: 'image-contain' // or 'image-cover', 'scale-montage'
})
// Export canvas as image
await editor.imageManager.exportCanvasAsImageFile({
fileName: 'export.png',
contentType: 'image/png' // 'image/png', 'image/jpeg', 'image/svg+xml', 'application/pdf'
})// Scale montage area to fit image
editor.canvasManager.scaleMontageAreaToImage()
// Set canvas dimensions
editor.canvasManager.setCanvasBackstoreWidth(800)
editor.canvasManager.setCanvasBackstoreHeight(600)
// Zoom operations
editor.canvas.zoomToPoint(point, zoomLevel)// Fit object to montage area
editor.transformManager.fitObject({
type: 'contain',
fitAsOneObject: true
})
// Reset object transformations
editor.transformManager.resetObject()
// Flip operations
editor.transformManager.flipX()
editor.transformManager.flipY()// Layer operations
editor.layerManager.sendToBack(object)
editor.layerManager.bringToFront(object)
editor.layerManager.sendBackwards(object)
editor.layerManager.bringForward(object)// Add a new shape group
const shape = await editor.shapeManager.add({
presetKey: 'hexagon',
options: {
text: 'Hello',
fill: '#2563eb',
width: 220,
height: 180
}
})
// Rebuild the same shape group with another preset
await editor.shapeManager.update({
target: shape,
presetKey: 'diamond',
options: {
text: 'Updated',
alignH: 'center',
alignV: 'middle'
}
})
// Shape-only styling
editor.shapeManager.setFill({ target: shape, fill: '#7c3aed' })
editor.shapeManager.setStroke({ target: shape, stroke: '#111827', strokeWidth: 3 })
editor.shapeManager.setOpacity({ target: shape, opacity: 0.85 })
// Text styling inside the shape
editor.shapeManager.updateTextStyle({
target: shape,
style: {
fontSize: 28,
color: '#ffffff',
bold: true
}
})
editor.shapeManager.setTextAlign({
target: shape,
horizontal: 'center',
vertical: 'middle'
})
await editor.shapeManager.setRounding({
target: shape,
rounding: 20
})
editor.shapeManager.remove({ target: shape })target can be omitted to use the active shape group. You can also pass the shape group itself, an inner Fabric object from that group, or the group's id string.
ShapeManager public methods:
add()creates a new shape group from a preset and optional text/style overrides.update()swaps the preset while preserving existing text, transforms, and object metadata.remove()deletes the shape group from canvas.setFill(),setStroke(), andsetOpacity()update the visual style of the outer shape node.getTextNode()returns the innerTextboxso it can be inspected or passed into other APIs.updateTextStyle()appliesTextManager-style updates to the inner text and recalculates the group layout.setTextAlign()changes horizontal and vertical alignment inside the shape bounds.setRounding()enables or updates corner rounding for roundable presets.
- Objects snap to montage area edges/centers and nearby objects while dragging, with guides for matches and equal spacing.
- Hold
Ctrlduring drag to temporarily disable snapping (movement still follows the configured move step). - Hold
Altwith an active selection to show measurement overlays to the hovered object or montage area; distances are labeled on the helper layer and the toolbar hides temporarily until guides clear.
// Undo/Redo
editor.historyManager.undo()
editor.historyManager.redo()
// Save state
editor.historyManager.saveState()
// Load from JSON
editor.historyManager.loadStateFromFullState(jsonState)// Serialize current selection to JSON template
const templateJson = editor.templateManager.serializeSelection({
includeBackground: false
})
// Apply template to canvas
await editor.templateManager.applyTemplate({
templateJson,
clearCanvas: false
})TemplateManager keeps layout fidelity by storing positions, styles, and (optionally) background data so you can rehydrate saved compositions.
# Development mode with demo app and watch
npm run dev
# Development build to dev-build folder
npm run dev:build
# Production build (library to dist/)
npm run build
# Build for GitHub Pages (demo to docs/)
npm run build:docs# Run all tests
npm test
# Watch mode for development
npm run test:watch
# Coverage report
npm run test:coverage
# CI mode
npm run test:cisrc/
├── main.ts # Entry point, exports initEditor()
├── editor/
│ ├── index.ts # ImageEditor class
│ ├── defaults.ts # Default configuration
│ ├── constants.ts # Constants and limits
│ ├── listeners.ts # Event handling
│ ├── background-manager/ # Background functionality
│ ├── canvas-manager/ # Canvas operations
│ ├── clipboard-manager/ # Copy/paste operations
│ ├── customized-controls/ # Custom FabricJS controls
│ ├── deletion-manager/ # Object deletion
│ ├── error-manager/ # Error handling
│ ├── grouping-manager/ # Object grouping
│ ├── history-manager/ # Undo/redo system
│ ├── image-manager/ # Image import/export
│ ├── interaction-blocker/ # UI blocking during operations
│ ├── layer-manager/ # Layer management
│ ├── module-loader/ # Dynamic module loading
│ ├── object-lock-manager/ # Object locking
│ ├── selection-manager/ # Selection handling
│ ├── shape-manager/ # Shape creation
│ ├── text-manager/ # Text objects and styling
│ ├── font-manager/ # Font loading utilities
│ ├── transform-manager/ # Object transformations
│ ├── worker-manager/ # Web Worker management
│ ├── ui/ # UI components (toolbar)
│ └── types/ # TypeScript definitions
├── editor/default-fonts.ts # Built-in Google font presets
├── demo/ # Demo application
specs/ # Test specifications
docs/ # GitHub Pages build output
dev-build/ # Development build output
dist/ # Production library build
vite.config.*.js # Vite configurations
jest.config.ts # Jest test configuration
The following features are planned for future releases:
- Drawing Mode - Freehand drawing tools and brushes
- Filters & Effects - Image filters and visual effects
- Extended Shape Library - Additional shapes beyond current rectangles, circles, and triangles
- Multi-language - Internationalization support
Contributions are welcome! Please feel free to submit a Pull Request.
git clone https://github.com/Anu3ev/image-editor.git
cd image-editor
npm install
npm run devnpm test # Run all tests
npm run test:watch # Development mode
npm run test:coverage # Coverage report- Chrome ≥ 88
- Firefox ≥ 85
- Safari ≥ 14
- Edge ≥ 88
All modern browsers with ES2016+ and Web Workers support.
MIT License - see LICENSE file for details.
- Built with FabricJS - Powerful HTML5 canvas library
- Vite - Lightning fast build tool
- TypeScript - Type safety and developer experience
- Jest - Comprehensive testing framework
Repository: github.com/Anu3ev/image-editor
NPM Package: @anu3ev/fabric-image-editor
Live Demo: anu3ev.github.io/image-editor