Plugin

croppy.js

Drop-in image cropper for <input type="file">. Pick a mode (fill / fit / resize), a shape, or drag a crop frame; the form submits the processed image — compressed to JPEG / WebP if you ask for it.

npm install @goboldlyforward/croppy

Try it

A real form, a real upload

Drop or pick an image, or click one of the samples. Toggle mode, shape, output format and quality — the Submit button below reports what the server would actually receive.

Form payload


    

Interactive crop

Drag the frame, type the coords

Opt in with data-crop="interactive" (fill mode only). The source image shows in the stage with a draggable crop frame — pan with the body, resize from the corners (aspect locked to target), or type exact pixel coordinates in the toolbar.

Button UI

When a drop zone is overkill

Set data-ui="button" for a compact, accented button instead of the full-width drop zone. Same pipeline, same toolbar after a file is picked.

Install & usage

Drop it in

One stylesheet, one script, one attribute. Auto-mounts on DOMContentLoaded.

<link rel="stylesheet" href="path/to/croppy.css">
<script src="path/to/croppy.js"></script>

<form action="/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="avatar"
         data-croppy
         data-ui="dropzone"       <!-- dropzone (default) | button -->
         data-mode="fill"         <!-- fill | fit | resize          -->
         data-shape="square"      <!-- square | portrait | landscape -->
         data-crop="interactive"  <!-- center (default) | interactive (fill only) -->
         data-format="image/jpeg" <!-- image/png | image/jpeg | image/webp -->
         data-quality="0.85"      <!-- 0..1, lossy formats only -->
         data-samples='[{"src":"/img/a.jpg","name":"A"}]'
         accept="image/*">
  <button>Save</button>
</form>

The original <input> stays in the DOM; its files list is replaced at pick time, so the form posts the processed image under the input's original name. No custom submit handler needed.

Modes

Fill, fit, resize

Same source image, three different jobs.

Fill

Cover the box. Crops what doesn't fit. Result = exact target size.

Fit

Contain inside the box. Bars fill the rest in the plugin's accent color.

Resize

Scale to fit under the max. No bars, no crop — output size matches the image.

JavaScript API

For when auto-init isn't enough

// Manual init
Croppy.initAll(scope);
Croppy.init(inputElement);

// Drive an instance
const inst = Croppy.instances.get(inputElement);
inst.setMode('fit');                  // 'fill' | 'fit' | 'resize'
inst.setShape('landscape');           // 'square' | 'portrait' | 'landscape'
inst.setFormat('image/webp');         // 'image/png' | 'image/jpeg' | 'image/webp'
inst.quality = 0.7; inst.render();    // re-encode with new quality
inst.loadSample({ src: '/img/x.jpg', name: 'X' });

// Read the current crop rect (interactive fill only)
inst.crop;  // { x, y, w, h } in source-pixel coords, or null

On every render the input's files list is replaced with a single File holding the processed image, so any normal form submit (or new FormData(form)) carries the cropped + compressed result.