@bensitu/image-editor 1.4.2 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +479 -130
- package/dist/image-editor.cjs +4185 -0
- package/dist/image-editor.cjs.map +7 -0
- package/dist/image-editor.esm.js +868 -377
- package/dist/image-editor.esm.js.map +3 -3
- package/dist/image-editor.esm.min.js +3 -3
- package/dist/image-editor.esm.min.js.map +3 -3
- package/dist/image-editor.esm.min.mjs +3 -3
- package/dist/image-editor.esm.min.mjs.map +3 -3
- package/dist/image-editor.esm.mjs +868 -377
- package/dist/image-editor.esm.mjs.map +3 -3
- package/dist/image-editor.js +868 -377
- package/dist/image-editor.js.map +3 -3
- package/dist/image-editor.min.js +2 -2
- package/dist/image-editor.min.js.map +3 -3
- package/image-editor.d.ts +64 -22
- package/package.json +4 -3
- package/src/image-editor.js +818 -267
package/README.md
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
# ImageEditor
|
|
2
|
+
|
|
2
3
|
[](https://github.com/bensitu/image-editor)
|
|
3
4
|
[](https://www.npmjs.com/package/@bensitu/image-editor)
|
|
4
5
|
[](https://www.jsdelivr.com/package/npm/@bensitu/image-editor)
|
|
5
6
|
|
|
6
|
-
A lightweight JavaScript wrapper around fabric.js that provides
|
|
7
|
+
A lightweight JavaScript wrapper around fabric.js that provides image loading, scaling, rotation, cropping, mask management, history, and export helpers.
|
|
7
8
|
|
|
8
9
|
## Overview
|
|
9
10
|
|
|
10
11
|
ImageEditor offers:
|
|
11
12
|
|
|
12
|
-
- Image loading from base64 or file input
|
|
13
|
-
- Zoom in/out and reset
|
|
14
|
-
- Rotation
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
13
|
+
- Image loading from base64 data URLs or file input
|
|
14
|
+
- Zoom in/out and reset transform helpers
|
|
15
|
+
- Rotation with custom degrees or step-based controls
|
|
16
|
+
- Crop mode with optional mask preservation
|
|
17
|
+
- Mask creation, selection, removal, and label support
|
|
18
|
+
- Undo/redo history helpers
|
|
19
|
+
- Merge, download, base64 export, and `File` export helpers
|
|
20
|
+
- Optional DOM/UI binding for common editor controls
|
|
21
|
+
- Large-image downsampling to reduce browser memory pressure
|
|
22
|
+
- Centralized error and warning callbacks
|
|
19
23
|
|
|
20
|
-
**Note
|
|
24
|
+
**Note:** This library requires **fabric.js v5.x** to be loaded before creating or initializing the editor.
|
|
21
25
|
|
|
22
26
|
## Demo
|
|
23
27
|
|
|
@@ -25,20 +29,41 @@ ImageEditor offers:
|
|
|
25
29
|
|
|
26
30
|
## Features
|
|
27
31
|
|
|
28
|
-
- **Fabric.js-powered canvas** - Built on top of
|
|
29
|
-
- **Image scaling** - Configurable min/max limits with smooth animation
|
|
30
|
-
- **Image rotation** - Step control and animated transitions
|
|
31
|
-
- **Auto-resizing** - Optional canvas resizing to match image or
|
|
32
|
-
- **Image crop** - Enter a crop mode
|
|
33
|
-
- **Mask management** - Add, remove, remove all,
|
|
34
|
-
- **Mask labels** - Auto-sync with mask movement
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
- **
|
|
32
|
+
- **Fabric.js-powered canvas** - Built on top of fabric.js.
|
|
33
|
+
- **Image scaling** - Configurable min/max limits with smooth animation.
|
|
34
|
+
- **Image rotation** - Step control and animated transitions.
|
|
35
|
+
- **Auto-resizing** - Optional canvas resizing to match the image, fit the viewport, or cover the viewport.
|
|
36
|
+
- **Image crop** - Enter a temporary crop mode and apply or cancel the crop.
|
|
37
|
+
- **Mask management** - Add, remove, remove all, drag, resize, and optionally rotate masks.
|
|
38
|
+
- **Mask labels** - Auto-sync labels with mask movement and scaling.
|
|
39
|
+
- **History** - Undo and redo supported editor state changes.
|
|
40
|
+
- **Performance optimization** - Downsample large images on load.
|
|
41
|
+
- **Export & download** - Export as base64 data URL, `File`, or direct download.
|
|
42
|
+
- **DOM/UI binding** - Bind common buttons, inputs, and placeholders by element ID.
|
|
38
43
|
|
|
39
44
|
## Installation
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
### npm / pnpm / yarn
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm i @bensitu/image-editor fabric
|
|
50
|
+
# or
|
|
51
|
+
pnpm add @bensitu/image-editor fabric
|
|
52
|
+
# or
|
|
53
|
+
yarn add @bensitu/image-editor fabric
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### ESM / bundler usage
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
import ImageEditor, {
|
|
60
|
+
ImageEditor as NamedImageEditor,
|
|
61
|
+
} from "@bensitu/image-editor";
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Browser global usage
|
|
65
|
+
|
|
66
|
+
Include fabric.js first, then ImageEditor:
|
|
42
67
|
|
|
43
68
|
```html
|
|
44
69
|
<!-- Fabric.js (required) -->
|
|
@@ -50,11 +75,7 @@ Include fabric.js and the ImageEditor class script in your HTML:
|
|
|
50
75
|
<script src="https://cdn.jsdelivr.net/npm/@bensitu/image-editor/dist/image-editor.min.js"></script>
|
|
51
76
|
```
|
|
52
77
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```javascript
|
|
56
|
-
import ImageEditor, { ImageEditor as NamedImageEditor } from '@bensitu/image-editor';
|
|
57
|
-
```
|
|
78
|
+
Use `dist/image-editor.js` or `dist/image-editor.min.js` for browser global script usage. Use `dist/image-editor.esm.mjs` or `dist/image-editor.esm.min.mjs` for standards-compliant ESM imports. Matching `.js` ESM builds are also generated for browser and bundler compatibility.
|
|
58
79
|
|
|
59
80
|
## Quick Start
|
|
60
81
|
|
|
@@ -65,149 +86,477 @@ import ImageEditor, { ImageEditor as NamedImageEditor } from '@bensitu/image-edi
|
|
|
65
86
|
<canvas id="fabricCanvas"></canvas>
|
|
66
87
|
|
|
67
88
|
<!-- Optional Controls -->
|
|
68
|
-
<button id="
|
|
69
|
-
<button id="
|
|
70
|
-
|
|
71
|
-
<
|
|
72
|
-
<
|
|
73
|
-
<input id="rotationRightInput" type="number" value="90">
|
|
89
|
+
<button id="zoomInButton">Zoom In</button>
|
|
90
|
+
<button id="zoomOutButton">Zoom Out</button>
|
|
91
|
+
|
|
92
|
+
<button id="rotateLeftButton">Rotate Left</button>
|
|
93
|
+
<input id="rotateLeftDegreesInput" type="number" value="90" />
|
|
74
94
|
|
|
75
|
-
<button id="
|
|
76
|
-
<
|
|
95
|
+
<button id="rotateRightButton">Rotate Right</button>
|
|
96
|
+
<input id="rotateRightDegreesInput" type="number" value="90" />
|
|
77
97
|
|
|
78
|
-
<button id="
|
|
79
|
-
<button id="
|
|
98
|
+
<button id="createMaskButton">Add Mask</button>
|
|
99
|
+
<button id="removeSelectedMaskButton">Remove Mask</button>
|
|
100
|
+
<button id="removeAllMasksButton">Remove All Masks</button>
|
|
80
101
|
|
|
81
|
-
<
|
|
102
|
+
<button id="enterCropModeButton">Crop</button>
|
|
103
|
+
<button id="applyCropButton">Apply Crop</button>
|
|
104
|
+
<button id="cancelCropButton">Cancel Crop</button>
|
|
105
|
+
|
|
106
|
+
<button id="undoButton">Undo</button>
|
|
107
|
+
<button id="redoButton">Redo</button>
|
|
108
|
+
|
|
109
|
+
<button id="mergeMasksButton">Merge</button>
|
|
110
|
+
<button id="resetImageTransformButton">Reset</button>
|
|
111
|
+
<button id="downloadImageButton">Download</button>
|
|
112
|
+
|
|
113
|
+
<input id="imageInput" type="file" accept="image/*" />
|
|
82
114
|
```
|
|
83
115
|
|
|
84
116
|
### JavaScript Implementation
|
|
85
117
|
|
|
118
|
+
The constructor options are optional. For the smallest setup, create an editor instance and bind it to a canvas element.
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
const editor = new ImageEditor();
|
|
122
|
+
|
|
123
|
+
editor.init({
|
|
124
|
+
canvas: "fabricCanvas"
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`canvas` is the only required DOM binding when your canvas element does not use the default ID `fabricCanvas`. All other DOM bindings are optional.
|
|
129
|
+
|
|
130
|
+
#### Optional Configuration
|
|
131
|
+
|
|
132
|
+
You can pass options to override the built-in defaults.
|
|
133
|
+
|
|
86
134
|
```javascript
|
|
87
|
-
// Create instance
|
|
88
135
|
const editor = new ImageEditor({
|
|
89
136
|
canvasWidth: 800,
|
|
90
137
|
canvasHeight: 600,
|
|
91
|
-
backgroundColor:
|
|
92
|
-
initialImageBase64: null
|
|
138
|
+
backgroundColor: "#ffffff",
|
|
139
|
+
initialImageBase64: null
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
These are common optional overrides, not required settings.
|
|
144
|
+
|
|
145
|
+
#### Demo-style Configuration
|
|
146
|
+
|
|
147
|
+
The docs demo uses an explicit configuration so the demo behavior is predictable. These options are not required for normal usage.
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
const editor = new ImageEditor({
|
|
151
|
+
// Layout mode. Enable only one layout mode at a time.
|
|
152
|
+
expandCanvasToImage: false,
|
|
153
|
+
fitImageToCanvas: true,
|
|
154
|
+
coverImageToCanvas: false,
|
|
155
|
+
|
|
156
|
+
// Image loading / performance.
|
|
157
|
+
downsampleOnLoad: true,
|
|
158
|
+
initialImageBase64: null,
|
|
159
|
+
|
|
160
|
+
// Mask behavior.
|
|
161
|
+
maskRotatable: true,
|
|
162
|
+
maskLabelOnSelect: true,
|
|
163
|
+
maskLabelOffset: 5,
|
|
164
|
+
|
|
165
|
+
// UI behavior.
|
|
166
|
+
backgroundColor: "transparent",
|
|
167
|
+
showPlaceholder: true,
|
|
168
|
+
animationDuration: 100,
|
|
169
|
+
|
|
170
|
+
// Export behavior.
|
|
171
|
+
exportImageAreaByDefault: true
|
|
93
172
|
});
|
|
94
173
|
|
|
95
|
-
// Initialize (binds to DOM elements)
|
|
96
174
|
editor.init({
|
|
97
|
-
canvas:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
175
|
+
canvas: "fabricCanvas",
|
|
176
|
+
imagePlaceholder: "imagePlaceholder",
|
|
177
|
+
scalePercentageInput: "scalePercentageInput",
|
|
178
|
+
rotateLeftButton: "rotateLeftButton",
|
|
179
|
+
rotateRightButton: "rotateRightButton",
|
|
180
|
+
rotateLeftDegreesInput: "rotateLeftDegreesInput",
|
|
181
|
+
rotateRightDegreesInput: "rotateRightDegreesInput",
|
|
182
|
+
removeSelectedMaskButton: "removeSelectedMaskButton",
|
|
183
|
+
removeAllMasksButton: "removeAllMasksButton",
|
|
184
|
+
mergeMasksButton: "mergeMasksButton",
|
|
185
|
+
downloadImageButton: "downloadImageButton",
|
|
186
|
+
maskList: "maskList",
|
|
187
|
+
enterCropModeButton: "enterCropModeButton",
|
|
188
|
+
applyCropButton: "applyCropButton",
|
|
189
|
+
cancelCropButton: "cancelCropButton",
|
|
190
|
+
resetImageTransformButton: "resetImageTransformButton"
|
|
108
191
|
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
### Loading an Image Manually
|
|
109
196
|
|
|
110
|
-
|
|
111
|
-
|
|
197
|
+
```javascript
|
|
198
|
+
async function loadImageDataUrl(imageBase64) {
|
|
199
|
+
try {
|
|
200
|
+
await editor.loadImage(imageBase64);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error("Image could not be loaded:", error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
loadImageDataUrl("data:image/jpeg;base64,...");
|
|
112
207
|
```
|
|
113
208
|
|
|
114
209
|
## Configuration Options
|
|
115
210
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
211
|
+
All options are optional. If an option is not provided, the editor uses the default value listed below.
|
|
212
|
+
|
|
213
|
+
When creating the editor instance, pass an options object to override defaults.
|
|
214
|
+
|
|
215
|
+
| Option | Default | Description |
|
|
216
|
+
| ----------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
217
|
+
| `canvasWidth` | `800` | Initial canvas width in pixels. |
|
|
218
|
+
| `canvasHeight` | `600` | Initial canvas height in pixels. |
|
|
219
|
+
| `backgroundColor` | `transparent` | Canvas background color. |
|
|
220
|
+
| `animationDuration` | `300` | Animation duration for scale and rotation operations, in milliseconds. |
|
|
221
|
+
| `minScale` | `0.1` | Minimum image scale factor. |
|
|
222
|
+
| `maxScale` | `5.0` | Maximum image scale factor. |
|
|
223
|
+
| `scaleStep` | `0.05` | Scale increment/decrement used by zoom controls. |
|
|
224
|
+
| `rotationStep` | `90` | Default rotation step in degrees. |
|
|
225
|
+
| `expandCanvasToImage` | `true` | Expand the canvas to the loaded image size. |
|
|
226
|
+
| `fitImageToCanvas` | `false` | Fit the loaded image inside the visible canvas viewport. |
|
|
227
|
+
| `coverImageToCanvas` | `false` | Scale the image to cover the visible canvas viewport, allowing overflow when needed. |
|
|
228
|
+
| `downsampleOnLoad` | `true` | Downsample large images before rendering. |
|
|
229
|
+
| `downsampleMaxWidth` | `4000` | Maximum source image width before downsampling. |
|
|
230
|
+
| `downsampleMaxHeight` | `3000` | Maximum source image height before downsampling. |
|
|
231
|
+
| `downsampleQuality` | `0.92` | JPEG/WebP quality used when downsampling. `0` is valid and is preserved. |
|
|
232
|
+
| `preserveSourceFormat` | `true` | Preserve the source image format where possible during downsampling. |
|
|
233
|
+
| `downsampleMimeType` | `null` | Optional output MIME type for downsampled images. Supported values include `jpeg`, `jpg`, `png`, `webp`, `image/jpeg`, `image/png`, and `image/webp`. |
|
|
234
|
+
| `imageLoadTimeoutMs` | `30000` | Timeout for image decode/load operations. |
|
|
235
|
+
| `exportMultiplier` | `1` | Default scale multiplier for export. |
|
|
236
|
+
| `exportImageAreaByDefault` | `true` | Export only the image area by default instead of the full canvas. |
|
|
237
|
+
| `defaultMaskWidth` | `50` | Default mask width in pixels. |
|
|
238
|
+
| `defaultMaskHeight` | `80` | Default mask height in pixels. |
|
|
239
|
+
| `maskRotatable` | `false` | Whether masks can be rotated. |
|
|
240
|
+
| `maskLabelOnSelect` | `true` | Show the mask label when a mask is selected. |
|
|
241
|
+
| `maskLabelOffset` | `3` | Offset for mask labels from the mask's top-left corner. |
|
|
242
|
+
| `maskName` | `mask` | Prefix for mask names and labels. |
|
|
243
|
+
| `groupSelection` | `false` | Whether Fabric can select multiple masks as an active selection. |
|
|
244
|
+
| `label.getText` | `(mask) => mask.maskName` | Callback for custom label text. The second argument is the mask's stable zero-based creation index (`mask.maskId - 1`). |
|
|
245
|
+
| `showPlaceholder` | `true` | Show a placeholder when no image is loaded. |
|
|
246
|
+
| `initialImageBase64` | `null` | Base64 data URL to auto-load during initialization. |
|
|
247
|
+
| `defaultDownloadFileName` | `edited_image.jpg` | Default file name for downloads. |
|
|
248
|
+
| `crop.preserveMasksAfterCrop` | `false` | Keep masks that intersect the crop area, shifted into the cropped canvas. Merge masks first if they should be baked into the image pixels. |
|
|
249
|
+
| `onImageLoaded` | `null` | Callback invoked after an image finishes loading. |
|
|
250
|
+
| `onError` | `null` | Callback invoked for recoverable internal errors. |
|
|
251
|
+
| `onWarning` | `null` | Callback invoked for recoverable internal warnings. |
|
|
149
252
|
|
|
150
253
|
`expandCanvasToImage`, `fitImageToCanvas`, and `coverImageToCanvas` are mutually exclusive layout modes. If more than one is enabled, the editor reports a warning and uses the first active mode in its existing load order.
|
|
151
254
|
|
|
255
|
+
## DOM Binding Keys
|
|
256
|
+
|
|
257
|
+
`init(idMap)` binds editor behavior to DOM elements by ID. All keys are optional.
|
|
258
|
+
|
|
259
|
+
| Key | Description |
|
|
260
|
+
| --------------------------- | -------------------------------------------------------------- |
|
|
261
|
+
| `canvas` | Required canvas element ID. |
|
|
262
|
+
| `canvasContainer` | Optional scrollable viewport/container element ID. |
|
|
263
|
+
| `imagePlaceholder` | Optional placeholder element shown when no image is loaded. |
|
|
264
|
+
| `scalePercentageInput` | Optional element used to display the current scale percentage. |
|
|
265
|
+
| `rotateLeftDegreesInput` | Optional input used by the rotate-left button. |
|
|
266
|
+
| `rotateRightDegreesInput` | Optional input used by the rotate-right button. |
|
|
267
|
+
| `rotateLeftButton` | Rotate image left. |
|
|
268
|
+
| `rotateRightButton` | Rotate image right. |
|
|
269
|
+
| `createMaskButton` | Create a new mask. |
|
|
270
|
+
| `removeSelectedMaskButton` | Remove the currently selected mask. |
|
|
271
|
+
| `removeAllMasksButton` | Remove all masks. |
|
|
272
|
+
| `mergeMasksButton` | Merge masks into the base image. |
|
|
273
|
+
| `downloadImageButton` | Download the edited image. |
|
|
274
|
+
| `maskList` | Optional mask list container. |
|
|
275
|
+
| `zoomInButton` | Zoom in. |
|
|
276
|
+
| `zoomOutButton` | Zoom out. |
|
|
277
|
+
| `resetImageTransformButton` | Reset scale and rotation. |
|
|
278
|
+
| `undoButton` | Undo the last state change. |
|
|
279
|
+
| `redoButton` | Redo the next state change. |
|
|
280
|
+
| `imageInput` | File input used to load images. |
|
|
281
|
+
| `uploadArea` | Optional clickable upload area that triggers the file input. |
|
|
282
|
+
| `enterCropModeButton` | Enter crop mode. |
|
|
283
|
+
| `applyCropButton` | Apply the current crop rectangle. |
|
|
284
|
+
| `cancelCropButton` | Cancel crop mode. |
|
|
285
|
+
|
|
286
|
+
### Legacy DOM Binding Keys
|
|
287
|
+
|
|
288
|
+
The following DOM binding keys remain supported in `1.x` for compatibility, but are deprecated and will be removed in `v2.0.0`.
|
|
289
|
+
|
|
290
|
+
| Deprecated key | Use instead |
|
|
291
|
+
| -------------------- | --------------------------- |
|
|
292
|
+
| `imgPlaceholder` | `imagePlaceholder` |
|
|
293
|
+
| `scaleRate` | `scalePercentageInput` |
|
|
294
|
+
| `rotationLeftInput` | `rotateLeftDegreesInput` |
|
|
295
|
+
| `rotationRightInput` | `rotateRightDegreesInput` |
|
|
296
|
+
| `rotateLeftBtn` | `rotateLeftButton` |
|
|
297
|
+
| `rotateRightBtn` | `rotateRightButton` |
|
|
298
|
+
| `addMaskBtn` | `createMaskButton` |
|
|
299
|
+
| `removeMaskBtn` | `removeSelectedMaskButton` |
|
|
300
|
+
| `removeAllMasksBtn` | `removeAllMasksButton` |
|
|
301
|
+
| `mergeBtn` | `mergeMasksButton` |
|
|
302
|
+
| `downloadBtn` | `downloadImageButton` |
|
|
303
|
+
| `zoomInBtn` | `zoomInButton` |
|
|
304
|
+
| `zoomOutBtn` | `zoomOutButton` |
|
|
305
|
+
| `resetBtn` | `resetImageTransformButton` |
|
|
306
|
+
| `undoBtn` | `undoButton` |
|
|
307
|
+
| `redoBtn` | `redoButton` |
|
|
308
|
+
| `cropBtn` | `enterCropModeButton` |
|
|
309
|
+
| `applyCropBtn` | `applyCropButton` |
|
|
310
|
+
| `cancelCropBtn` | `cancelCropButton` |
|
|
311
|
+
|
|
152
312
|
## API Methods
|
|
153
313
|
|
|
154
|
-
| Method | Description
|
|
155
|
-
|
|
156
|
-
| `init(idMap)` | Bind the editor to DOM elements. Pass IDs in an object
|
|
157
|
-
| `loadImage(imageBase64, options)` | Load an image from a base64 data
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
314
|
+
| Method | Returns | Description |
|
|
315
|
+
| --------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
316
|
+
| `init(idMap)` | `void` | Bind the editor to DOM elements. Pass IDs in an object. |
|
|
317
|
+
| `loadImage(imageBase64, options)` | `Promise<void>` | Load an image from a base64 data URL. Resolves after the Fabric image is on the canvas. |
|
|
318
|
+
| `isImageLoaded()` | `boolean` | Return whether a valid image is loaded on the canvas. |
|
|
319
|
+
| `isBusy()` | `boolean` | Return whether the editor is loading, animating, cropping, or running another compound operation. |
|
|
320
|
+
| `scaleImage(factor)` | `Promise<void>` | Scale the image to the given factor relative to the base scale. |
|
|
321
|
+
| `rotateImage(degrees)` | `Promise<void>` | Rotate the image to the given angle in degrees. |
|
|
322
|
+
| `resetImageTransform()` | `Promise<void>` | Reset scale to `1` and rotation to `0`. |
|
|
323
|
+
| `undo()` | `Promise<void>` | Undo the last state change. Resolves after the canvas state is restored. |
|
|
324
|
+
| `redo()` | `Promise<void>` | Redo the next state change. Resolves after the canvas state is restored. |
|
|
325
|
+
| `createMask(config)` | `fabric.Object \| null` | Create a mask on the canvas. Config can include shape, width, height, color, opacity, position, style, and callbacks. |
|
|
326
|
+
| `removeSelectedMask()` | `void` | Remove the currently selected mask or selected masks. |
|
|
327
|
+
| `removeAllMasks(options)` | `void` | Remove all masks from the canvas. |
|
|
328
|
+
| `enterCropMode()` | `void` | Create a resizable/movable selection rectangle on top of the image. |
|
|
329
|
+
| `cancelCrop()` | `void` | Cancel crop mode and remove the temporary selection rectangle. |
|
|
330
|
+
| `applyCrop()` | `Promise<void>` | Apply the current crop rectangle to the canvas. |
|
|
331
|
+
| `mergeMasks()` | `Promise<void>` | Merge masks into the base image on the canvas. |
|
|
332
|
+
| `downloadImage(fileName)` | `void` | Download the edited image as a file. |
|
|
333
|
+
| `exportImageBase64(options)` | `Promise<string>` | Export an image data URL. `fileType` can be `jpeg`, `jpg`, `png`, `webp`, or a supported image MIME type. |
|
|
334
|
+
| `exportImageFile(options)` | `Promise<File>` | Export the current canvas as a `File` object. |
|
|
335
|
+
| `saveState()` | `void` | Save the current editor state to history. Usually called internally after supported edits. |
|
|
336
|
+
| `loadFromState(serializedState)` | `Promise<void>` | Restore the editor from a serialized canvas/editor state. |
|
|
337
|
+
|
|
338
|
+
Deprecated method aliases are still available for compatibility and will be removed in `v2.0.0`.
|
|
339
|
+
|
|
340
|
+
| Deprecated method | Use instead |
|
|
341
|
+
| ------------------------- | ---------------------------- |
|
|
342
|
+
| `reset()` | `resetImageTransform()` |
|
|
343
|
+
| `addMask(config)` | `createMask(config)` |
|
|
344
|
+
| `merge()` | `mergeMasks()` |
|
|
345
|
+
| `getImageBase64(options)` | `exportImageBase64(options)` |
|
|
346
|
+
|
|
347
|
+
## Mask Configuration
|
|
348
|
+
|
|
349
|
+
`createMask(config)` accepts an optional configuration object.
|
|
350
|
+
|
|
351
|
+
| Option | Description |
|
|
352
|
+
| ------------------ | ------------------------------------------------------------------------------------------ |
|
|
353
|
+
| `shape` | Mask shape. Built-in values include `rect`, `circle`, `ellipse`, and `polygon`. |
|
|
354
|
+
| `width` / `height` | Mask size in pixels, percentage string, or resolver callback. |
|
|
355
|
+
| `radius` | Circle radius in pixels, percentage string, or resolver callback. |
|
|
356
|
+
| `rx` / `ry` | Ellipse radius or rectangle corner radius. |
|
|
357
|
+
| `points` | Polygon points as `{ x, y }` objects or `[x, y]` tuples. |
|
|
358
|
+
| `left` / `top` | Mask position in pixels, percentage string, or resolver callback. |
|
|
359
|
+
| `angle` | Initial rotation angle in degrees. |
|
|
360
|
+
| `color` | Fill color. |
|
|
361
|
+
| `alpha` | Opacity from `0` to `1`. |
|
|
362
|
+
| `selectable` | Whether the mask can be selected. |
|
|
363
|
+
| `hasControls` | Whether Fabric transform controls are shown. |
|
|
364
|
+
| `styles` | Additional Fabric style properties, such as `stroke`, `strokeWidth`, or `strokeDashArray`. |
|
|
365
|
+
| `fabricGenerator` | Factory callback for creating a custom Fabric object. |
|
|
366
|
+
| `onCreate` | Callback invoked after the mask is added to the canvas. |
|
|
367
|
+
|
|
368
|
+
Example:
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
const mask = editor.createMask({
|
|
372
|
+
shape: "rect",
|
|
373
|
+
width: 120,
|
|
374
|
+
height: 80,
|
|
375
|
+
left: 20,
|
|
376
|
+
top: 20,
|
|
377
|
+
color: "rgba(0, 0, 0, 0.5)",
|
|
378
|
+
alpha: 0.5,
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Crop Behavior
|
|
383
|
+
|
|
384
|
+
By default, applying crop removes unmerged masks.
|
|
385
|
+
|
|
386
|
+
This is intentional: an unmerged mask is still an editable overlay object, not part of the image pixels. When `crop.preserveMasksAfterCrop` is `false`, applying crop discards unmerged masks instead of trying to keep or partially crop those overlay objects.
|
|
387
|
+
|
|
388
|
+
Choose the workflow based on the result you want:
|
|
389
|
+
|
|
390
|
+
- **Crop first, then add masks** when masks should be placed only on the final cropped image.
|
|
391
|
+
- **Merge masks before cropping** when masks should become part of the image pixels and appear in the cropped result.
|
|
392
|
+
- **Enable `crop.preserveMasksAfterCrop`** when masks should remain editable after crop. Only masks that intersect the crop area are kept and shifted into the cropped canvas.
|
|
393
|
+
- **Keep the default `crop.preserveMasksAfterCrop: false`** when unmerged masks should be discarded by crop.
|
|
394
|
+
|
|
395
|
+
### Preserve editable masks after crop
|
|
396
|
+
|
|
397
|
+
```javascript
|
|
398
|
+
const editor = new ImageEditor({
|
|
399
|
+
crop: {
|
|
400
|
+
preserveMasksAfterCrop: true,
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Bake masks into the image before crop
|
|
406
|
+
|
|
407
|
+
```javascript
|
|
408
|
+
try {
|
|
409
|
+
await editor.mergeMasks();
|
|
410
|
+
editor.enterCropMode();
|
|
411
|
+
await editor.applyCrop();
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.error("Crop workflow failed:", error);
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Export Examples
|
|
418
|
+
|
|
419
|
+
### Export as Base64
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
try {
|
|
423
|
+
const dataUrl = await editor.exportImageBase64({
|
|
424
|
+
fileType: "jpeg",
|
|
425
|
+
quality: 0.92,
|
|
426
|
+
multiplier: 1,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
console.log(dataUrl);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
console.error("Export failed:", error);
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Export as File
|
|
436
|
+
|
|
437
|
+
```javascript
|
|
438
|
+
try {
|
|
439
|
+
const file = await editor.exportImageFile({
|
|
440
|
+
fileName: "edited_image.jpg",
|
|
441
|
+
fileType: "jpeg",
|
|
442
|
+
quality: 0.92,
|
|
443
|
+
mergeMask: true,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
console.log(file);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
console.error("File export failed:", error);
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Error Handling
|
|
453
|
+
|
|
454
|
+
Some ImageEditor API methods may throw synchronously or reject their returned Promise when the operation cannot be completed.
|
|
455
|
+
|
|
456
|
+
Recommended usage:
|
|
457
|
+
|
|
458
|
+
- Wrap initialization and manual API calls in `try...catch`.
|
|
459
|
+
- Always `await` Promise-based methods such as `loadImage()`, `scaleImage()`, `rotateImage()`, `resetImageTransform()`, `mergeMasks()`, `applyCrop()`, `undo()`, `redo()`, `exportImageBase64()`, `exportImageFile()`, and `loadFromState()`.
|
|
460
|
+
- Use `onError` and `onWarning` for centralized logging or UI notifications.
|
|
461
|
+
- Do not rely only on `onError`; manual API calls should still handle thrown errors or rejected Promises at the call site.
|
|
462
|
+
- Check `editor.isBusy()` before starting user-triggered operations when needed, especially before loading, cropping, rotating, scaling, merging, undoing, redoing, or exporting.
|
|
463
|
+
- Show user-friendly messages in your application instead of exposing raw error text directly.
|
|
464
|
+
|
|
465
|
+
Example:
|
|
466
|
+
|
|
467
|
+
```javascript
|
|
468
|
+
const editor = new ImageEditor({
|
|
469
|
+
onError: (error, message) => {
|
|
470
|
+
console.error("[ImageEditor]", message, error);
|
|
471
|
+
showEditorMessage("The image editor could not complete the operation.");
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
onWarning: (error, message) => {
|
|
475
|
+
console.warn("[ImageEditor]", message, error);
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
async function rotateSafely(degrees) {
|
|
480
|
+
if (editor.isBusy()) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
await editor.rotateImage(degrees);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
console.error("Rotate failed:", error);
|
|
488
|
+
showEditorMessage("Rotation failed. Please try again.");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function showEditorMessage(message) {
|
|
493
|
+
// Replace this with your own toast, alert, or inline message UI.
|
|
494
|
+
console.log(message);
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Common Failure Cases
|
|
499
|
+
|
|
500
|
+
Common failure cases include:
|
|
501
|
+
|
|
502
|
+
- fabric.js is not loaded before creating or initializing the editor.
|
|
503
|
+
- The configured canvas element cannot be found.
|
|
504
|
+
- The image data URL is invalid, unsupported, too large, or times out while loading.
|
|
505
|
+
- Another operation is already running, such as image loading, animation, crop, undo, redo, merge, or export.
|
|
506
|
+
- The editor has been disposed before the operation completes.
|
|
507
|
+
- The browser cannot create a canvas export because of platform, memory, or security restrictions.
|
|
508
|
+
- A custom `fabricGenerator`, label callback, or external event handler throws an error.
|
|
177
509
|
|
|
178
510
|
## Example Workflow
|
|
179
511
|
|
|
180
|
-
|
|
181
|
-
2. **Adjust positioning** - Zoom in/out or rotate as needed
|
|
182
|
-
3. **Add masks** - Highlight or cover specific areas (drag, resize)
|
|
183
|
-
4. **Merge result** - Create a flattened export (optional)
|
|
184
|
-
5. **Download / export** - Save the final image
|
|
512
|
+
The recommended order depends on whether masks should remain editable, be baked into the image, or be discarded.
|
|
185
513
|
|
|
186
|
-
|
|
514
|
+
### Common workflow: crop first, then add masks
|
|
187
515
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
516
|
+
Use this flow when masks are only needed on the final cropped image.
|
|
517
|
+
|
|
518
|
+
1. **Load an image** - Via file input or base64 data URL.
|
|
519
|
+
2. **Adjust positioning** - Zoom in/out, rotate, or reset as needed.
|
|
520
|
+
3. **Crop** - Optionally crop the image area.
|
|
521
|
+
4. **Add masks** - Highlight or cover specific areas on the cropped image.
|
|
522
|
+
5. **Merge result** - Flatten masks into the base image when needed.
|
|
523
|
+
6. **Download / export** - Save the final image.
|
|
524
|
+
|
|
525
|
+
### Alternative workflow: add masks before crop
|
|
526
|
+
|
|
527
|
+
If masks are added before crop, choose one of these behaviors before applying crop:
|
|
528
|
+
|
|
529
|
+
- Call `mergeMasks()` before `applyCrop()` if masks should become part of the cropped image pixels.
|
|
530
|
+
- Set `crop.preserveMasksAfterCrop: true` if masks should stay editable after crop.
|
|
531
|
+
- Keep the default `crop.preserveMasksAfterCrop: false` if unmerged masks should be removed by crop.
|
|
532
|
+
|
|
533
|
+
Example:
|
|
534
|
+
|
|
535
|
+
```javascript
|
|
536
|
+
// Masks become part of the image pixels before crop.
|
|
537
|
+
await editor.mergeMasks();
|
|
538
|
+
editor.enterCropMode();
|
|
539
|
+
await editor.applyCrop();
|
|
195
540
|
```
|
|
196
541
|
|
|
197
|
-
|
|
542
|
+
## Local Build
|
|
543
|
+
|
|
198
544
|
```bash
|
|
199
545
|
npm run build
|
|
200
546
|
```
|
|
201
547
|
|
|
202
|
-
|
|
203
|
-
|
|
548
|
+
## Tests
|
|
549
|
+
|
|
550
|
+
```bash
|
|
551
|
+
npm test
|
|
552
|
+
```
|
|
204
553
|
|
|
205
554
|
## Browser Support
|
|
206
555
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
556
|
+
- Chrome 100+
|
|
557
|
+
- Firefox 100+
|
|
558
|
+
- Safari 15+
|
|
559
|
+
- Edge 100+
|
|
211
560
|
|
|
212
561
|
The package build targets these modern browsers and uses modern DOM and JavaScript features.
|
|
213
562
|
|
|
@@ -215,7 +564,7 @@ IE11 and old mobile Safari are not supported by the distributed build. If you ne
|
|
|
215
564
|
|
|
216
565
|
## Dependencies
|
|
217
566
|
|
|
218
|
-
- **fabric.js v5.x** — Must be loaded before ImageEditor
|
|
567
|
+
- **fabric.js v5.x** — Must be loaded before ImageEditor.
|
|
219
568
|
|
|
220
569
|
## License
|
|
221
570
|
|