A compound-component Angular image cropper. Accessible, headless, and fully customizable.
Ported from @origin-space/image-cropper (React).
- Compound component pattern -- compose only what you need
- Mouse drag, scroll wheel zoom, keyboard arrows, touch & pinch support
- Controlled or uncontrolled zoom
- Outputs crop coordinates in original image pixels
- Accessible by default (ARIA attributes, screen reader description)
- SSR-safe
- Zero dependencies beyond Angular
pnpm add image-cropperPeer dependencies: @angular/core and @angular/common (v19, 20, or 21).
import {
CropperComponent,
CropperImageComponent,
CropperCropAreaComponent,
CropperDescriptionComponent,
Area,
} from 'image-cropper';
@Component({
imports: [
CropperComponent,
CropperImageComponent,
CropperCropAreaComponent,
CropperDescriptionComponent,
],
template: `
<sim-cropper class="h-80" image="/photos/example.jpg" (cropChange)="onCrop($event)">
<sim-cropper-description class="sr-only" />
<sim-cropper-image />
<sim-cropper-crop-area />
</sim-cropper>
`,
})
export class MyComponent {
onCrop(area: Area | null) {
console.log(area);
// { x: 120, y: 80, width: 400, height: 400 }
}
}That's it. The crop area comes with sensible defaults (white border, dark overlay, centered). Override with your own classes or styles.
Wraps everything. Handles state, events, and resize observation.
| Input | Type | Default | Description |
|---|---|---|---|
image |
string |
required | Image URL |
aspectRatio |
number |
1 |
Crop area width/height ratio |
cropPadding |
number |
25 |
Min padding from container edges (px) |
minZoom |
number |
1 |
Minimum zoom level |
maxZoom |
number |
3 |
Maximum zoom level |
zoomSensitivity |
number |
0.005 |
Scroll wheel zoom multiplier |
keyboardStep |
number |
10 |
Pixels per arrow key press |
zoom |
number |
-- | Controlled zoom (bypasses internal state) |
| Output | Type | Description |
|---|---|---|
cropChange |
Area | null |
Crop coordinates in original image pixels |
zoomChange |
number |
Current zoom level |
Renders the image with transform and scale applied. No inputs needed.
Visual crop boundary. Default: white border, dark semi-transparent overlay, centered. Style it however you want -- add rounded-full for a circle, change border color, etc.
Screen reader instructions, linked via aria-describedby. Default text provided. Visually hidden by default.
<!-- Custom text -->
<sim-cropper-description class="sr-only"> Scroll to zoom, drag to pan. </sim-cropper-description>type Area = {
x: number; // Top-left X in original image pixels
y: number; // Top-left Y in original image pixels
width: number; // Crop width in original image pixels
height: number; // Crop height in original image pixels
};| Input | Action |
|---|---|
| Mouse drag | Pan |
| Scroll wheel | Zoom at pointer |
| Arrow keys | Pan (step = keyboardStep) |
| Touch drag | Pan |
| Pinch | Zoom at pinch center |
<sim-cropper [image]="url" [zoom]="zoom" (zoomChange)="zoom = $event" (cropChange)="onCrop($event)">
<sim-cropper-description class="sr-only" />
<sim-cropper-image />
<sim-cropper-crop-area />
</sim-cropper>
<input type="range" [min]="1" [max]="3" step="0.01" [(ngModel)]="zoom" /><sim-cropper [image]="url" [aspectRatio]="16 / 9" (cropChange)="onCrop($event)"> ... </sim-cropper><sim-cropper-crop-area class="rounded-full" /><sim-cropper-crop-area class="border-blue-500" />pnpm install # Install dependencies
pnpm build:lib # Build the library
pnpm start # Run the demo app (localhost:4200)
pnpm test # Unit tests (Vitest)
pnpm e2e # E2E visual regression tests (Playwright)
pnpm e2e:ui # Playwright interactive UI
pnpm e2e:update # Regenerate screenshot baselinesMIT