Image Cropper
Headless image cropper: aspect-locked crop box, zoom, rotation, keyboard nudging, canvas export.
| Package | @vizejs/ui/image-cropper |
| Maturity | stable |
| Own the source | vize lib pull image-cropper |
| Requires | context, controllable-state, id, measure |
| Aliases | image cropper, crop, avatar cropper, photo editor, image crop |
| Covers | Cropper.js, react-easy-crop, react-image-crop, Ark UI Image Cropper |
Usage
import { ImageCropperArea, ImageCropperGrid, ImageCropperHandle, ImageCropperImage, ImageCropper, ImageCropperRoot, ImageCropperViewport } from "@vizejs/ui/image-cropper";
Or copy the source into your project with vize lib pull image-cropper (see Source Distribution).
API
ImageCropperArea
Source: src/families/media/image-cropper/image-cropper-area.vue
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
ImageCropperSlotState |
Handles, grid, and other overlays inside the crop box. Receives the cropper state. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
ImageCropperGrid
Source: src/families/media/image-cropper/image-cropper-grid.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
divisions |
number |
3 |
Number of equal columns and rows; 3 draws the rule of thirds. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
ImageCropperSlotState |
Optional extra overlay content. Receives the cropper state. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
ImageCropperHandle
Source: src/families/media/image-cropper/image-cropper-handle.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
position (required) |
ImageCropperHandlePosition |
required |
Edge or corner this handle resizes, by compass direction. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
ImageCropperSlotState |
Optional handle content. Receives the cropper state. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
ImageCropperImage
Source: src/families/media/image-cropper/image-cropper-image.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
src (required) |
string |
required |
Image source. Relative, https:, blob:, and base64 data:image/* URLs are accepted; unsafe schemes are dropped. Use CORS-enabled URLs to export crops. |
allowInsecure |
boolean |
false |
Permit unencrypted http: sources for local development. |
alt (required) |
string |
required |
Native alternative text. |
crossOrigin |
"" | "anonymous" | "use-credentials" |
undefined |
Native CORS mode; anonymous keeps canvas exports untainted for CORS images. |
referrerPolicy |
ReferrerPolicy |
undefined |
Native referrer policy. |
Events
| Event | Payload | Description |
|---|---|---|
load |
[nativeEvent: Event | null] |
Fired after the image loads and its natural size is known. |
error |
[nativeEvent: Event] |
Fired when the image fails to load. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
ImageCropper
Source: src/families/media/image-cropper/image-cropper-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
modelValue |
CropArea |
undefined |
Controlled crop in rotated-image pixels (v-model). undefined selects uncontrolled behavior. |
defaultValue |
CropArea |
undefined |
Initial crop for uncontrolled use. undefined centers a crop covering autoCropArea. |
zoom |
number |
undefined |
Controlled zoom factor (v-model:zoom); 1 fits the whole image. |
defaultZoom |
number |
1 |
Initial zoom for uncontrolled use. |
rotation |
number |
undefined |
Controlled rotation in degrees (v-model:rotation). |
defaultRotation |
number |
0 |
Initial rotation for uncontrolled use. |
aspectRatio |
number |
undefined |
Locked width / height ratio. undefined allows free resizing. |
minWidth |
number |
undefined |
Minimum crop width in image pixels. |
minHeight |
number |
undefined |
Minimum crop height in image pixels. |
maxWidth |
number |
undefined |
Maximum crop width in image pixels. |
maxHeight |
number |
undefined |
Maximum crop height in image pixels. |
minZoom |
number |
1 |
Smallest zoom factor. |
maxZoom |
number |
5 |
Largest zoom factor. |
zoomStep |
number |
0.1 |
Relative zoom change per key press or wheel notch. |
rotationStep |
number |
90 |
Degrees rotated by [ and ]. |
nudgeStep |
number |
1 |
Image pixels moved per arrow key press (Shift multiplies by 10). |
autoCropArea |
number |
0.8 |
Fraction of the image the initial centered crop covers. |
disabled |
boolean |
false |
Suppress every pointer and keyboard interaction. |
messages |
Partial<ImageCropperMessages> |
undefined |
Localized accessible text. Missing keys fall back to English. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue |
[crop: CropArea] |
Fired when the crop requests a new controlled value. |
update:zoom |
[zoom: number] |
Fired when zoom requests a new controlled value. |
update:rotation |
[rotation: number] |
Fired when rotation requests a new controlled value. |
change |
[crop: CropArea, reason: ImageCropperChangeReason] |
Fired after every distinct crop request, with its cause. |
cropEnd |
[crop: CropArea] |
Fired when a pointer interaction or key press finishes changing the crop. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
ImageCropperSlotState |
Viewport, image, area, handles, and controls. Receives the cropper state. |
Exposed
| Member | Type | Description |
|---|---|---|
crop |
— | |
disabled |
— | |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
interaction |
— | |
naturalSize |
— | |
ready |
— | |
reset |
— | |
rotation |
— | |
setCrop |
— | |
setRotation |
— | |
setZoom |
— | |
toBlob |
— | |
toDataUrl |
— | |
zoom |
— |
ImageCropperRoot
Source: src/families/media/image-cropper/image-cropper-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
modelValue |
CropArea |
undefined |
Controlled crop in rotated-image pixels (v-model). undefined selects uncontrolled behavior. |
defaultValue |
CropArea |
undefined |
Initial crop for uncontrolled use. undefined centers a crop covering autoCropArea. |
zoom |
number |
undefined |
Controlled zoom factor (v-model:zoom); 1 fits the whole image. |
defaultZoom |
number |
1 |
Initial zoom for uncontrolled use. |
rotation |
number |
undefined |
Controlled rotation in degrees (v-model:rotation). |
defaultRotation |
number |
0 |
Initial rotation for uncontrolled use. |
aspectRatio |
number |
undefined |
Locked width / height ratio. undefined allows free resizing. |
minWidth |
number |
undefined |
Minimum crop width in image pixels. |
minHeight |
number |
undefined |
Minimum crop height in image pixels. |
maxWidth |
number |
undefined |
Maximum crop width in image pixels. |
maxHeight |
number |
undefined |
Maximum crop height in image pixels. |
minZoom |
number |
1 |
Smallest zoom factor. |
maxZoom |
number |
5 |
Largest zoom factor. |
zoomStep |
number |
0.1 |
Relative zoom change per key press or wheel notch. |
rotationStep |
number |
90 |
Degrees rotated by [ and ]. |
nudgeStep |
number |
1 |
Image pixels moved per arrow key press (Shift multiplies by 10). |
autoCropArea |
number |
0.8 |
Fraction of the image the initial centered crop covers. |
disabled |
boolean |
false |
Suppress every pointer and keyboard interaction. |
messages |
Partial<ImageCropperMessages> |
undefined |
Localized accessible text. Missing keys fall back to English. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue |
[crop: CropArea] |
Fired when the crop requests a new controlled value. |
update:zoom |
[zoom: number] |
Fired when zoom requests a new controlled value. |
update:rotation |
[rotation: number] |
Fired when rotation requests a new controlled value. |
change |
[crop: CropArea, reason: ImageCropperChangeReason] |
Fired after every distinct crop request, with its cause. |
cropEnd |
[crop: CropArea] |
Fired when a pointer interaction or key press finishes changing the crop. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
ImageCropperSlotState |
Viewport, image, area, handles, and controls. Receives the cropper state. |
Exposed
| Member | Type | Description |
|---|---|---|
crop |
CropArea | null |
Current crop, or null until the image size is known. |
zoom |
number |
Current zoom factor (1 fits the whole image). |
rotation |
number |
Current rotation in degrees, normalized to [0, 360). |
naturalSize |
CropSize | null |
Natural image size, or null until the image loads. |
ready |
boolean |
Whether image and viewport are measured and geometry is published. |
interaction |
ImageCropperInteraction |
Pointer or keyboard interaction in progress. |
disabled |
boolean |
Whether every interaction is suppressed. |
element |
HTMLDivElement | null |
Rendered root element. |
setCrop |
(crop: CropArea) => boolean |
Request a crop (clamped to the constraints). Reports whether it changed. |
setZoom |
(zoom: number) => boolean |
Request a zoom factor (clamped). Reports whether it changed. |
setRotation |
(degrees: number) => boolean |
Request a rotation in degrees. Reports whether it changed. |
reset |
() => void |
Restore the centered default crop, zoom 1, and rotation 0. |
toBlob |
(options?: Omit<CropImageOptions, "rotation">) => Promise<Blob> |
Crop the loaded image into a Blob with the current crop and rotation (client-only). |
toDataUrl |
(options?: Omit<CropImageOptions, "rotation">) => Promise<string> |
Crop the loaded image into a data URL with the current crop and rotation (client-only). |
ImageCropperViewport
Source: src/families/media/image-cropper/image-cropper-viewport.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
wheelZoom |
boolean |
true |
Zoom around the pointer with the mouse wheel or trackpad. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
ImageCropperSlotState |
Image, crop area, and overlays. Receives the cropper state. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
Behavior
Normative state x input -> outcome table for image-cropper-root.vue,
image-cropper-viewport.vue, image-cropper-image.vue, image-cropper-area.vue,
image-cropper-handle.vue, and image-cropper-grid.vue (@vizejs/ui/image-cropper).
Every row is proven by the named test.
Crops are { x, y, width, height } in the rotated image's bounding box, measured
in natural image pixels, so cropImage and server-side croppers can reproduce
the result. The viewport shows the bounds with contain scale times zoom;
geometry is published only after the image and viewport are measured, so server
and hydration markup agree.
| ID | State | Input | Outcome | Evidence |
|---|---|---|---|---|
| IC1 | unmeasured | image load | the centered autoCropArea crop is requested (init); image and area receive measured positions; the area is a labelled focusable group |
publishes the initial centered crop and measured geometry once the image loads |
| IC2 | ready | drag the area | the crop moves by pointer delta / scale, stays in bounds, reports moving, and emits cropEnd on release; secondary buttons are ignored |
dragging the area moves the crop in image pixels and commits on release |
| IC3 | ready | drag a handle | the opposite edge stays anchored; locked aspect ratios resize symmetrically around the crop center | handles resize from their edge or corner, including aspect-locked crops |
| IC4 | focused area | arrows / Alt+arrows / + - / [ ] |
arrows nudge by nudgeStep (Shift ×10), Alt/Ctrl/Meta+arrows resize, +/- zoom around the crop center, [/] rotate by rotationStep |
keyboard nudges, resizes, zooms, and rotates the focused crop area |
| IC5 | ready | wheel / background drag | the wheel zooms around the pointer; dragging outside the area pans the zoomed view within the image | wheel zoom keeps the pointer anchor and background drags pan the zoomed view |
| IC6 | ready | rotation change | bounds swap on right angles and the crop keeps its relative center, clamped into the new bounds (rotate) |
rotation swaps the bounds and re-fits the crop around its relative center |
| IC7 | controlled | crop / zoom / rotation requests | requests are emitted while controlled values render; zoom clamps to minZoom..maxZoom; controlled crops are not re-initialized |
controlled crop, zoom, and rotation win until the parent accepts them |
| IC8 | any | reset() / messages / disabled |
reset restores zoom, rotation, and the default crop; messages localize the accessible text; disabled croppers ignore every input | reset, localized messages, and disabled croppers |
| IC9 | image | unsafe / cached / replaced source | unsafe sources are dropped, cached images are read on mount, and a different image size re-initializes the crop | images are sanitized, read when already complete, and reset the crop when replaced |
| IC10 | grid | render | divisions - 1 lines per axis expose offsets through --vize-ui-image-cropper-grid-offset |
the grid renders division lines with offsets |
| IC11 | exposed instance | toBlob() / toDataUrl() |
exports use the current crop and rotation through cropImage, and reject before an image loads |
exports the loaded crop through the canvas helper |
| IC12 | missing provider | setup | compound parts fail closed with the shared context diagnostic | compound parts require a matching root provider |
| IC13 | geometry | rotation | rotation normalizes into [0, 360) and right angles swap bounds exactly |
normalizes rotation and computes rotated bounds exactly on right angles |
| IC14 | geometry | clamp | crops clamp into bounds, size limits, and aspect ratios; bounds win over minimums | clamps crops into bounds, size limits, and aspect ratios |
| IC15 | geometry | default crop | default crops are centered by coverage and aspect ratio | centers default crops by coverage and aspect ratio |
| IC16 | geometry | move | moves stay inside the bounds | moves crops by deltas without leaving the bounds |
| IC17 | geometry | free resize | every handle resizes with an anchored opposite edge, never crossing and honoring limits | resizes free crops from every handle with anchored opposite edges |
| IC18 | geometry | locked resize | corners follow the dominant axis; edges grow symmetrically; bounds and limits cap the size | resizes aspect-locked crops along the dominant axis and within bounds |
| IC19 | geometry | re-fit | re-fitting keeps the relative center and falls back to a centered crop from empty bounds | re-fits crops after bounds change by keeping the relative center |
| IC20 | geometry | view mapping | contain scale, clamped view centers, viewport mapping, and zoom anchors are exact | maps the view with contain scale, clamped centers, and zoom anchors |
| IC21 | canvas helper | crop | the image is rotated into its bounds, cut, downscaled to maxWidth/maxHeight, and encoded as a Blob or data URL |
crops, rotates, downscales, and encodes blobs and data URLs |
| IC22 | canvas helper | URL source | URL sources load with the requested CORS mode before cropping; load errors reject | loads URL sources with CORS before cropping |
| IC23 | canvas helper | failure | empty crops, missing canvases, load failures, and encode failures reject with typed ImageCropperError codes |
rejects empty crops, missing canvases, failed loads, and failed encodes with typed errors |
| IC24 | canvas helper | pending image | pending image elements are awaited | waits for pending images to load |
| IC25 | SSR | isolated requests | markup is byte-identical and carries no measured geometry | renders byte-identical cropper markup without measured geometry on the server |
| IC26 | SSR / hydration | hydrate | server markup hydrates without warnings or node replacement | hydrates cropper markup without warnings or node replacement |
| IC27 | types | compile | crops, handles, reasons, interactions, export overloads, and exposes are closed and read-only | image-cropper.types.test-d.ts |
Pinch-to-zoom gestures are not interpreted; touch users zoom with the consumer's
controls (setZoom) or the keyboard.