quickcrop

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.

Try it

With themodal

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 });

Install

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

Usage

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

Options

optiondefaultmeaning
aspectnullwidth / height lock; null = free crop
typesmartoutput mime; keeps the input type for jpeg/png/webp, else png
quality0.92encoder quality for jpeg/webp
maxWidthnullcap output width in px, downscales proportionally
maxHeightnullcap output height; with maxWidth, stricter wins
minSize40minimum crop box edge in display px
labels{ confirm: 'Crop' }confirm button text
modal'auto''auto' | 'builtin' | themodal instance | custom adapter

Bring your own modal

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 });

Theming

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.