Crop an image before upload. One function, no dependencies, returns a Blob.
Pick a file, drag the crop box, confirm. You get back a Blob sized for upload
plus a dataURL for instant previews. It uses your modal system
(themodal or any custom adapter) or brings its own.
The same cropper hosted by
themodal from hyperclayjs.
With modal: 'auto' (the default) this happens by itself on any page where
window.themodal exists; here it is passed explicitly.
const { default: themodal } = await import(
'https://cdn.jsdelivr.net/npm/hyperclayjs@latest/src/ui/theModal.js'
);
const result = await quickcrop(file, { modal: themodal });
Served straight from npm by jsDelivr. The @1 pin tracks 1.x releases. The script injects its own styles; link the stylesheet only if you want to theme via CSS.
<script type="module">
import quickcrop from 'https://cdn.jsdelivr.net/npm/quickcrop@1/quickcrop.js';
</script>
npm install quickcrop
const result = await quickcrop(file, { aspect: 1, maxWidth: 512 });
if (result) {
// result.blob, result.dataURL, result.width, result.height
const form = new FormData();
form.append('avatar', result.blob, 'avatar.jpg');
await fetch('/upload', { method: 'POST', body: form });
}
// result is null when the user cancels
| option | default | meaning |
|---|---|---|
aspect | null | width / height lock; null = free crop |
type | smart | output mime; keeps the input type for jpeg/png/webp, else png |
quality | 0.92 | encoder quality for jpeg/webp |
maxWidth | null | cap output width in px, downscales proportionally |
maxHeight | null | cap output height; with maxWidth, stricter wins |
minSize | 40 | minimum crop box edge in display px |
labels | { confirm: 'Crop' } | confirm button text |
modal | 'auto' | 'auto' | 'builtin' | themodal instance | custom adapter |
An adapter normalizes any modal system to one function. quickcrop hands you the live crop stage; you place it, wire two callbacks, and return a close().
const myAdapter = {
open({ content, confirmLabel, onConfirm, onCancel }) {
const dialog = document.createElement('dialog');
dialog.append(content); // the live crop stage
// ...your confirm button wired to onConfirm, dismissal to onCancel...
document.body.append(dialog);
dialog.showModal();
return { close() { dialog.close(); dialog.remove(); } };
},
fit() { return { width: innerWidth * .8, height: innerHeight * .7 }; }
};
await quickcrop(file, { modal: myAdapter });
The default look is the warm "pixel quiet" palette. Override the --qc-* variables to retheme:
:root {
--qc-surface: #17191d; /* modal + button background */
--qc-border: #2a2e35; /* borders */
--qc-text: #e7e9ee; /* text + primary button background */
--qc-on-dark: #0f1012; /* text on the primary button */
}
All variables: --qc-surface, --qc-surface-hover, --qc-border, --qc-text, --qc-text-hover, --qc-on-dark, --qc-overlay, --qc-dim, --qc-radius, --qc-crop-line, --qc-font.