Image Cropper

Crop an image and get the result as a Blob.

Installation

Import the class, and @use the entry point in your global styles: without it the component renders unstyled.

import { Component } from '@angular/core';
import { WrImageCropper } from 'ngwr/image-cropper';

@Component({ imports: [WrImageCropper] })
export class MyComponent {}

Basic

Drag the box to move it and a handle to resize it; (cropped) emits a Blob after each drag.

<input type="file" accept="image/*" (change)="onFileChange($event)" />

<wr-image-cropper [src]="src()" (cropped)="onCropped($event)" />

Square (1:1)

aspectRatio locks the shape of the box.

<wr-image-cropper [src]="avatar" [aspectRatio]="1" />

Load errors

An unreadable source fires (loadError) and sets status() to 'failed'; the cropper shows no message, so show your own. A URL on another origin needs CORS headers, or it fails the same way.

<wr-image-cropper #cropper [src]="file()" (loadError)="lastError.set($event)" />

@if (cropper.status() === 'failed') {
  <wr-alert type="danger" title="This image could not be opened" [message]="message()" />
}
import type { WrImageLoadError } from 'ngwr/image-cropper';

protected readonly file = signal<File | null>(null);
protected readonly lastError = signal<WrImageLoadError | null>(null);

// The error has the file's name and type, not a reason.
protected readonly message = computed(() => {
  const error = this.lastError();
  return `${error?.name ?? 'The file'} is not an image this browser can read.`;
});

Export size

maxOutputSize caps the longest side of the exported image; a smaller crop is not upscaled.

<wr-image-cropper [src]="file()" [aspectRatio]="1" [maxOutputSize]="512" (cropped)="upload($event)" />

Keyboard

Focus the crop box to move and resize it from the keyboard.

KeyDoes
Arrow keysMove by 1px.
Shift + arrowsMove by 10px.
Alt + arrowsResize from the right or bottom edge: Left and Right change the width, Up and Down the height.
Shift + Alt + arrowsResize by 10px.

API

NameDescriptionTypeDefault
srcImage source.string | File | Blob | nullnull
aspectRatioLock width / height. null = free.number | nullnull
minWidthMin crop width in display px.number32
minHeightMin crop height in display px.number32
outputTypeMIME type for (cropped).'image/png' | 'image/jpeg' | 'image/webp''image/png'
outputQualityJPEG / WebP quality in [0, 1].number0.92
maxOutputSizeLongest side of the exported image in pixels. null exports at the source resolution.number | nullnull
(cropped)Emits a Blob after each drag end.Blob—
(loadError)Fires when the browser cannot decode src, with { url, name, type, size }.WrImageLoadError—
Readable stateSignals and methods on the component instance.members—
cropRectComputed crop rect in source-image pixel coordinates.Signal<WrCropRect>—
status'empty', 'loading', 'loaded' or 'failed'. Resets when src changes.Signal<WrImageCropperStatus>—
refresh()Re-measure and re-emit (cropped) after the cropper was resized.() => Promise<void>—
toBlob() / toDataUrl()Read the current crop programmatically.method—

CSS variables

Set these on the component's selector or a wrapper, not on :root: the defaults are declared on ngwr/image-cropper's own element.

VariableDefaultDeclared on
--wr-image-cropper-empty-bordervar(--wr-border-base).wr-image-cropper__empty
--wr-image-cropper-empty-paddingvar(--wr-space-xl).wr-image-cropper__empty
--wr-image-cropper-empty-radiusvar(--wr-border-radius-base).wr-image-cropper__empty
--wr-image-cropper-font-familyvar(--wr-font-family-base).wr-image-cropper

See also