Use <crop-image> with zero framework dependencies.
Drop a single script tag and the element is ready to use. No build step required.
<script src="https://unpkg.com/@imazen/crop-image/dist/crop-image.iife.js"></script>
<crop-image src="/photo.jpg"></crop-image>
npm install @imazen/crop-image
<script type="module">
import '@imazen/crop-image';
</script>
<crop-image src="/photo.jpg"></crop-image>
All configuration happens through HTML attributes. Changes are reflected immediately.
| Attribute | Type | Default | Description |
|---|---|---|---|
src | string | — | Image URL to crop |
mode | crop | crop-pad | crop | Crop-only or crop with letterbox padding |
aspect-ratio | string | free | Lock ratio: 16/9, 1:1, or 1.5 |
shape | rect | circle | rect | Frame shape. Circle forces 1:1 aspect ratio. |
max-zoom | number | auto | Maximum zoom level (default: image size / 50px) |
snap-threshold | number | 0.03 | Snap-to-AR sensitivity during free resize (0 = off, 0.03 = 3%) |
aspect-ratios | JSON | — | Menu of choices: [{"w":16,"h":9,"label":"Wide"}] |
min-width | number | — | Minimum crop width in source pixels |
min-height | number | — | Minimum crop height in source pixels |
max-width | number | — | Maximum crop width in source pixels |
max-height | number | — | Maximum crop height in source pixels |
value | JSON | — | Set selection as CropSelection JSON |
name | string | — | Form field name (enables form participation) |
adapter | string | generic | RIAPI adapter: generic, imageflow, imageresizer |
disabled | boolean | false | Disable all interaction |
The element fires two custom events. Both carry the same detail shape.
| Event | When |
|---|---|
crop-change | Continuously during pan, zoom, or frame resize |
crop-commit | Once when a gesture ends (pointer up, scroll stop, slider change) |
Both events bubble and are composed (cross Shadow DOM). The detail object contains:
{
selection: {
crop: { x1, y1, x2, y2 }, // 0..1 fractions of source image
pad: { top, right, bottom, left } // 0..1 fractions (crop-pad mode)
},
riapi: {
params: { crop: "...", cropxunits: "1", ... },
querystring: "?crop=0.1,0.1,0.9,0.9&cropxunits=1&cropyunits=1"
}
}
const cropper = document.querySelector('crop-image');
cropper.addEventListener('crop-change', (e) => {
// Live updates during drag
console.log('Selection:', e.detail.selection);
console.log('RIAPI:', e.detail.riapi.querystring);
});
cropper.addEventListener('crop-commit', (e) => {
// Final value when user releases
const qs = e.detail.riapi.querystring;
document.getElementById('output').textContent = qs;
});
Use the selection property to set the crop from JavaScript:
const cropper = document.querySelector('crop-image');
// Set a centered 50% crop
cropper.selection = {
crop: { x1: 0.25, y1: 0.25, x2: 0.75, y2: 0.75 },
pad: { top: 0, right: 0, bottom: 0, left: 0 }
};
Or use the value attribute with JSON:
<crop-image
src="/photo.jpg"
value='{"crop":{"x1":0.1,"y1":0.1,"x2":0.9,"y2":0.9},"pad":{"top":0,"right":0,"bottom":0,"left":0}}'
></crop-image>
const cropper = document.querySelector('crop-image');
// Current selection (CropSelection object)
const sel = cropper.selection;
// RIAPI querystring for the current crop
const qs = cropper.riapiQuerystring;
// RIAPI params as an object
const params = cropper.riapiParams;
// Current config (read-only snapshot)
const config = cropper.config;
const cropper = document.querySelector('crop-image');
// Lock to 16:9
cropper.setAttribute('aspect-ratio', '16/9');
// Circle crop (forces 1:1)
cropper.setAttribute('shape', 'circle');
// Limit zoom to 4x
cropper.setAttribute('max-zoom', '4');
// Switch to crop-pad mode
cropper.setAttribute('mode', 'crop-pad');
// Change image
cropper.setAttribute('src', '/another-photo.jpg');
// Use Imageflow adapter
cropper.setAttribute('adapter', 'imageflow');
Style the crop UI through CSS custom properties on the element:
crop-image {
--crop-overlay-color: rgba(0, 0, 0, 0.65);
--crop-border-color: rgba(255, 255, 255, 0.85);
--crop-border-width: 2px;
--crop-pad-color: rgba(80, 140, 220, 0.25);
--crop-slider-track: rgba(255, 255, 255, 0.3);
--crop-slider-thumb: #fff;
}
Click or tab to focus the crop element, then use:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/@imazen/crop-image/dist/crop-image.iife.js"></script>
<style>
crop-image { max-width: 600px; }
</style>
</head>
<body>
<crop-image
src="/photo.jpg"
aspect-ratio="16/9"
mode="crop"
max-zoom="8"
></crop-image>
<p>Querystring: <code id="output"></code></p>
<script>
document.querySelector('crop-image')
.addEventListener('crop-commit', (e) => {
document.getElementById('output').textContent =
e.detail.riapi.querystring;
});
</script>
</body>
</html>