ImageEditor

The all-in-one ImageEditor component provides a full-featured editing toolbar driven by a plugin system. Crop, filter, tune, resize, frame and watermark — all in one component.

Import

import { ImageEditor } from '@we-are-singular/svelte-chop-chop';

Props

PropTypeDefaultDescription
srcImageSourceImage URL, data URL, File, Blob, or HTMLImageElement.
pluginsChopPlugin[][]Plugin instances that add toolbar tabs and functionality.
aspectRationumber | { min?: number; max?: number } | nullnullInitial crop aspect ratio.
classstring''Extra CSS class on the root element.

Events

EventTypeDescription
onexport(blob: Blob) => voidFires when the user presses the export/save button. Receives the exported Blob.
oncancel() => voidFires when the user presses cancel/close.

Full example with all plugins

<script lang="ts">
  import { ImageEditor } from '@we-are-singular/svelte-chop-chop';
  import { pluginFilters } from '@we-are-singular/svelte-chop-chop/plugins/filters';
  import { pluginFinetune } from '@we-are-singular/svelte-chop-chop/plugins/finetune';
  import { pluginFrame } from '@we-are-singular/svelte-chop-chop/plugins/frame';
  import { pluginWatermark } from '@we-are-singular/svelte-chop-chop/plugins/watermark';
  import { pluginResize } from '@we-are-singular/svelte-chop-chop/plugins/resize';
  import '@we-are-singular/svelte-chop-chop/themes/default';

  function handleExport(blob: Blob) {
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = 'edited.jpg';
    link.click();
    URL.revokeObjectURL(url);
  }
</script>

<ImageEditor
  src="/photo.jpg"
  plugins={[pluginFilters(), pluginFinetune(), pluginFrame(), pluginWatermark(), pluginResize()]}
  onexport={handleExport}
/>

Upload to server example

<script lang="ts">
  import { ImageEditor } from '@we-are-singular/svelte-chop-chop';
  import { pluginFilters } from '@we-are-singular/svelte-chop-chop/plugins/filters';

  async function handleExport(blob: Blob) {
    const formData = new FormData();
    formData.append('image', blob, 'photo.jpg');
    await fetch('/api/upload', { method: 'POST', body: formData });
  }
</script>

<ImageEditor
  src="/photo.jpg"
  plugins={[pluginFilters()]}
  onexport={handleExport}
/>

Keyboard shortcuts

The editor accepts keyboard input when focused:

KeyAction
RRotate 90° clockwise
Shift+RRotate 90° counter-clockwise
HFlip horizontal
VFlip vertical
Ctrl+ZUndo
Ctrl+Shift+ZRedo
Arrow keysMove crop 1px (10px with Shift)
+ / -Zoom in / out
0Reset zoom (fit to view)
EscapeCancel / close

Headless composable

For full UI control, use createImageEditor from the headless export. It returns a reactive state object with all actions:

import { createImageEditor } from '@we-are-singular/svelte-chop-chop/headless';
import { pluginFilters } from '@we-are-singular/svelte-chop-chop/plugins/filters';
import { pluginFinetune } from '@we-are-singular/svelte-chop-chop/plugins/finetune';

const editor = createImageEditor({
  plugins: [pluginFilters(), pluginFinetune()],
});

// Load an image
await editor.loadImage('/photo.jpg');

// Apply a filter
editor.applyFilter('clarendon');

// Adjust finetune
editor.setFinetune('brightness', 20);
editor.setFinetune('contrast', 10);

// Rotate and flip
editor.rotate(90);
editor.flipX();

// Undo / redo
editor.undo();

// Export
const result = await editor.export({
  format: 'image/webp',
  quality: 0.9,
  maxWidth: 1920,
});

// result.blob, result.file, result.dataURL, result.canvas
// result.coordinates, result.transforms, result.originalSize

Export options

OptionTypeDefaultDescription
format'image/jpeg' | 'image/png' | 'image/webp''image/png'MIME type for the output image.
qualitynumber0.92Compression quality 0–1 (JPEG/WebP only).
maxWidthnumberMaximum output width. Downscales proportionally if exceeded.
maxHeightnumberMaximum output height. Downscales proportionally if exceeded.
shape'rect' | 'circle''rect'Crop shape. 'circle' applies a circular mask with transparent corners. Automatically uses PNG if JPEG was selected (JPEG doesn't support transparency).
postProcess(ctx, canvas) => void | PromiseHook to draw on the canvas after rendering but before encoding (e.g. custom watermark).

ExportResult type

interface ExportResult {
  canvas?: HTMLCanvasElement;
  blob?: Blob;
  file?: File;
  dataURL?: string;
  coordinates: CropCoordinates;
  transforms: TransformState;
  originalSize: { width: number; height: number };
}