Image Cropping in jQuery with Jcrop (and the Modern Cropper.js Way)

Blog / JavaScript · January 13, 2015 · Updated June 10, 2026 · 7 min read
Image Cropping in jQuery with Jcrop (and the Modern Cropper.js Way)

To crop an image in the browser with jQuery, the classic approach is the Jcrop plugin: call $('#target').Jcrop() on an <img>, read the selected x/y/w/h coordinates from its onSelect callback, and POST those numbers to your server to perform the real crop. Jcrop still works, but it is dated and hard-depends on jQuery. For new projects in 2026, reach for Cropper.js instead — it is vanilla JavaScript, touch-friendly, and hands you the cropped pixels directly as a <canvas>/Blob you can upload. Either way, always do the authoritative crop server-side (for example with Python's Pillow) so a tampered request can't bypass your rules.

Key takeaways

  • Jcrop is a jQuery plugin that turns an <img> into a crop selector and reports the selection coordinates; it works but is no longer actively developed.
  • For new work, prefer Cropper.js (vanilla JS, no jQuery, touch and pinch-to-zoom) or a framework wrapper such as react-cropper.
  • Client-side cropping is only a preview. Send either the coordinates or the cropped Blob to the server and crop there with Pillow (Image.crop).
  • Never trust client-supplied dimensions — clamp coordinates to the real image bounds before cropping.
  • Libraries like sorl-thumbnail or easy-thumbnails can generate resized variants once the crop is saved.

How does Jcrop crop an image in jQuery?

Jcrop attaches to a plain <img> element and draws a draggable selection box over it. Start with the image you want to crop, then load jQuery, the Jcrop script, and its stylesheet:

<!-- The image to crop -->
<img id="target" src="/media/pool.jpg" alt="Photo to crop" />

<!-- jQuery + Jcrop (classic 0.9.x plugin) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Jcrop/0.9.12/css/jquery.Jcrop.min.css" />
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Jcrop/0.9.12/js/jquery.Jcrop.min.js"></script>

Initialise Jcrop on the image and listen for onSelect. The callback receives an object with the selection's x, y, x2, y2, w, and h — those are the coordinates you forward to the server. You can constrain the selection with aspectRatio, minSize, and maxSize:

let lastCrop = null;

jQuery(function ($) {
  $('#target').Jcrop({
    aspectRatio: 1, // 1 = square; omit for free-form
    minSize: [50, 50], // [width, height] in pixels
    maxSize: [800, 800],
    onSelect: (c) => {
      // c.x, c.y = top-left corner; c.w, c.h = size of the selection
      lastCrop = { x: c.x, y: c.y, w: c.w, h: c.h };
    },
    onRelease: () => {
      lastCrop = null; // selection cleared
    },
  });
});

// Send the chosen coordinates to the server to do the real crop
async function saveCrop() {
  if (!lastCrop) return;
  await fetch('/images/42/crop/', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(lastCrop),
  });
}

Should you still use Jcrop in 2026?

Honestly, for a brand-new project, no. Jcrop is reliable and easy to learn, but it has seen little maintenance and forces a full jQuery dependency onto pages that may not otherwise need one. The wider jQuery ecosystem is winding down — see our take on jQuery's overview and its future.

The de-facto modern replacement is Cropper.js: dependency-free vanilla JavaScript, touch and pinch-to-zoom on mobile, rotation and scaling, and — crucially — it gives you the cropped pixels directly as a <canvas> you can export to a Blob. If you build with React, the react-cropper wrapper exposes the same engine as a component.

How do you crop an image with Cropper.js?

Point Cropper.js at an <img> inside a sized container and create a Cropper instance. There is no jQuery anywhere. Load it from a CDN for a quick page:

<div style="max-width: 600px;">
  <img id="target" src="/media/pool.jpg" alt="Photo to crop" />
</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cropperjs@1.6.2/dist/cropper.min.css" />
<script src="https://cdn.jsdelivr.net/npm/cropperjs@1.6.2/dist/cropper.min.js"></script>

In a bundled app (Vite, webpack, SvelteKit) install it with npm install cropperjs, then import the class and its stylesheet:

// crop.js
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';

const image = document.querySelector('#target');

const cropper = new Cropper(image, {
  aspectRatio: 1, // 1 = square; NaN for free-form
  viewMode: 1, // keep the crop box inside the image
  autoCropArea: 0.8,
  responsive: true,
});

How do you upload the cropped result to the server?

When the user confirms, ask Cropper.js for the cropped region as a <canvas>, convert it to a Blob, and POST it as multipart form data with fetch. Include Django's CSRF token in the request header:

/**
 * Export the current crop and upload it as a file.
 * @param {Cropper} cropper - the active Cropper.js instance
 * @param {number} imageId - id of the image record being cropped
 */
async function uploadCrop(cropper, imageId) {
  const canvas = cropper.getCroppedCanvas({
    width: 512,
    height: 512,
    imageSmoothingQuality: 'high',
  });

  // canvas.toBlob is async; wrap it in a Promise
  const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', 0.9));

  const form = new FormData();
  form.append('cropped', blob, `crop-${imageId}.jpg`);

  await fetch(`/images/${imageId}/crop/`, {
    method: 'POST',
    headers: { 'X-CSRFToken': getCookie('csrftoken') },
    body: form, // do NOT set Content-Type; the browser adds the boundary
  });
}

Why must the real crop happen on the server?

A determined user can edit any request before it leaves the browser, so client-side cropping is only a convenience preview — never a security boundary. The server must produce the final image. You have two clean options:

  1. Send coordinates (from Jcrop, or Cropper's getData()) and crop with Pillow.
  2. Send the cropped Blob and just validate, re-encode, and store it.

Either way, validate the file type and clamp the coordinates to the real image dimensions.

# views.py - crop from coordinates with Pillow
import json
from pathlib import Path

from PIL import Image
from django.conf import settings
from django.http import JsonResponse
from django.views.decorators.http import require_POST


@require_POST
def crop_from_coords(request, image_id):
    data = json.loads(request.body)
    src = Path(settings.MEDIA_ROOT) / "pool.jpg"

    with Image.open(src) as img:
        width, height = img.size

        # Clamp every value to the real image bounds - never trust the client
        x = max(0, int(data["x"]))
        y = max(0, int(data["y"]))
        w = min(int(data["w"]), width - x)
        h = min(int(data["h"]), height - y)

        cropped = img.crop((x, y, x + w, y + h))
        cropped.save(src.with_name(f"crop-{image_id}.jpg"), quality=90)

    return JsonResponse({"ok": True})

If you uploaded the already-cropped Blob instead, the view is even simpler — re-open it through Pillow to confirm it really is an image and to strip metadata, then save:

# views.py - store an already-cropped upload safely
from PIL import Image, UnidentifiedImageError
from django.http import JsonResponse, HttpResponseBadRequest
from django.views.decorators.http import require_POST


@require_POST
def store_cropped_blob(request, image_id):
    upload = request.FILES.get("cropped")
    if not upload:
        return HttpResponseBadRequest("No file")

    try:
        # Pillow verifies it really is an image, then re-encodes it
        with Image.open(upload) as img:
            img = img.convert("RGB")
            img.thumbnail((1024, 1024))  # cap the stored size
            img.save(f"media/crop-{image_id}.jpg", "JPEG", quality=90)
    except UnidentifiedImageError:
        return HttpResponseBadRequest("Not a valid image")

    return JsonResponse({"ok": True})

Don't want to manage every size by hand? Libraries like sorl-thumbnail or easy-thumbnails generate and cache resized or further-cropped derivatives from the stored original on demand, so you only persist one authoritative crop.

Jcrop vs Cropper.js vs the native approach

Feature Jcrop Cropper.js Native <canvas>
jQuery dependency Required None None
Touch / pinch-zoom Limited Built-in Do it yourself
Maintenance Largely dormant Actively used N/A (browser API)
Direct cropped output Coordinates only <canvas>Blob <canvas>Blob
Rotate / scale / zoom No Yes Do it yourself
Bundle weight jQuery + plugin ~40 KB, standalone 0 (built in)
Best for Legacy jQuery apps New projects Tiny, fixed crops

Related guides

MicroPyramid has shipped 50+ products over 12+ years (since 2014); secure image handling and upload pipelines are part of nearly every one.

Frequently Asked Questions

Is Jcrop still maintained in 2026?

Jcrop still works and remains easy to learn, but it sees little active development and depends entirely on jQuery. For new projects, the maintained, dependency-free choice is Cropper.js. Keep Jcrop only if you're extending an existing jQuery codebase where adding it costs nothing extra.

What is the best modern alternative to Jcrop?

Cropper.js is the de-facto standard: vanilla JavaScript, no jQuery, touch and pinch-to-zoom support, rotation and scaling, and it returns the cropped region as a <canvas> you can export to a Blob. For React apps, react-cropper wraps the same engine as a component.

How do I get the cropped image as a file to upload?

With Cropper.js, call cropper.getCroppedCanvas() to get a <canvas>, then canvas.toBlob(callback, 'image/jpeg', 0.9) to get a Blob. Append that Blob to a FormData object and POST it with fetch. Don't set the Content-Type header manually — the browser adds the correct multipart boundary for you.

Should I crop on the client or the server?

Both, for different reasons. Crop on the client for instant visual feedback, but always perform the authoritative crop on the server, because anyone can tamper with a request before it is sent. Send either the coordinates or the cropped blob, then validate and re-encode it server-side.

How do I crop an image in Django with Pillow?

Open the image with Image.open(), call img.crop((left, top, right, bottom)) using box coordinates, and save() the result. Always clamp the incoming x/y/w/h values to the real image dimensions first so a malicious request can't read outside the image. Re-encoding through Pillow also strips unwanted metadata.

Does Cropper.js work on mobile and touch devices?

Yes. Cropper.js supports touch dragging, pinch-to-zoom, and rotation out of the box, which is a major advantage over the older Jcrop plugin. Set viewMode and responsive: true so the crop box stays inside the image and adjusts when the screen rotates or resizes.

Share this article