Now.js Framework Documentation

Now.js Framework Documentation

ImageCropper - crop an image in the browser

EN 13 Nov 2026 12:43

ImageCropper - crop an image in the browser

Loaded on demand — part of the feature bundle now.imagecrop.min.js (+ .css), not of now.core.min.js: a file field with data-crop="true" loads it; code that uses ImageCropper directly calls await Now.require('ImageCropper') first — see Feature bundles

ImageCropper opens one image in a crop dialog, lets the user move, zoom and turn it under a frame, and returns the result as a new File.
The frame stays fixed at the requested aspect ratio and the image moves beneath it, always covering the whole frame, so the result never has empty edges.

The usual way in is FileElementFactory, opened by data-crop="true" on a file field.
This page covers the cropper itself, for calling it from code.

Table of Contents

  1. Usage
  2. Options
  3. Controls in the crop dialog
  4. The resulting file
  5. Cropping again from the original
  6. CSS

Usage

await Now.require('ImageCropper');

const file = await ImageCropper.open(input.files[0], {aspect: '3/4', width: 600});
if (file) {
  // the cropped File
} else {
  // the user pressed Cancel, the close button or Escape
}

ImageCropper.open(source, options)

Parameter Type Description
source File, Blob or string The image to crop, or its URL (same origin, or one served with CORS), data:image/... included
options Object Overrides for ImageCropper.config (see Options)

Returns Promise<File|null> — null when the user cancels.
Rejects with an Error (translated message "Unable to open this image") when the image cannot be opened: not an image, or a type the browser cannot read.

One crop dialog is shown at a time. open() calls may overlap; the next image waits until the previous dialog has closed.

Options

Every option takes a number or the string a data attribute carries.

Option Default Description
aspect 1 Frame width/height: 1, 0.75, '3/4', '3:4', '16/9'; a value that cannot be read is 1
width 0 Width of the result in px, height from the aspect · 0 = the cropped area's own size, at most maxWidth
maxWidth 2048 Width limit when width is 0
shape 'rect' 'rect' or 'circle' — circle only changes the frame's mask; the file is always a rectangle
format 'auto' 'auto', 'jpeg', 'png', 'webp' — auto keeps a JPEG, PNG or WebP source's type, anything else becomes JPEG
quality 0.9 JPEG and WebP quality (0-1)
minWidth 0 Warn in the dialog when the selected area holds fewer source pixels across than this · 0 = never
maxZoom 4 Furthest zoom, relative to the zoom at which the image just covers the frame
title 'Crop image' Dialog title (translated with Now.translate())

Controls in the crop dialog

Action Mouse / touch Keyboard (focus on the crop area)
Move the image Drag Arrow keys (hold Shift for bigger steps)
Zoom Wheel, two-finger pinch, the slider, the zoom out/in buttons + / -
Turn 90° clockwise Rotate button -
Back to the whole image, unturned Reset button -
Confirm OK button Enter
Cancel Cancel button or the close button Escape

Wheel and pinch zoom keep the point under the pointer in place · Escape closes the crop dialog only, not a modal open beneath it.
The crop area refits its frame whenever it changes size (turning a phone or resizing the window keeps the selection).

The resulting file

  • width × round(width / aspect) px (or the cropped area's own size when width is 0)
  • The source's own name while the type is unchanged · a new type changes the extension (photo.png + format: 'jpeg' = photo.jpg)
  • A source without a name (Blob, data: URL) is called image + extension
  • Browsers that cannot write WebP (Safari) produce JPEG instead
  • JPEG has no transparency: transparent parts of the source become white
  • A camera photo's orientation (EXIF) is already applied when the image opens

Cropping in the browser is not a security boundary: the server must still check the file type and save the image anew
(Kotchasan's UploadedFile::cropImage() / resizeImage(), for example).

Cropping again from the original

A File ImageCropper produced remembers its source and how it was cropped.
Passing that File to open() again opens the original at the same position, zoom and turn, so the image is not resampled each time it is edited.

let photo = await ImageCropper.open(original, {aspect: 1, width: 400});
// later: edit the crop
photo = await ImageCropper.open(photo, {aspect: 1, width: 400}) || photo;

CSS

File Now/css/image-cropper.css (in now.imagecrop.min.css)

Class What it is
.image-cropper-modal The crop dialog's modal
.image-cropper-stage The crop area (takes mouse, touch and keyboard) · .dragging while dragging
.image-cropper-image The image moving under the frame
.image-cropper-frame The frame · .circle with shape: 'circle'
.image-cropper-toolbar Buttons and the zoom slider
.image-cropper-zoom The zoom slider
.image-cropper-warning The warning when the selection is narrower than minWidth