Source Code

<div class="container py-5 d-flex justify-content-center">
  <div class="card bscrop-card">
    <div class="card-body p-4">
      <label class="form-label small fw-semibold">Choose an image</label>
      <input type="file" class="form-control mb-3" id="bscropInput" accept="image/*">

      <div class="d-none" id="bscropEditor">
        <div class="bscrop-frame" id="bscropFrame">
          <img id="bscropImg" draggable="false" alt="">
        </div>
        <label class="form-label small mt-2 mb-1">Zoom</label>
        <input type="range" class="form-range" id="bscropZoom" min="1" max="2.5" step="0.01" value="1">
        <button type="button" class="btn btn-dark btn-sm fw-bold mt-2" id="bscropSave">Save crop</button>
      </div>

      <div class="d-none mt-3" id="bscropResultWrap">
        <p class="small fw-semibold mb-1">Cropped result:</p>
        <canvas id="bscropCanvas" width="160" height="160" class="bscrop-result"></canvas>
      </div>
    </div>
  </div>
</div>

Bootstrap Image Crop Before Upload — Free HTML CSS JS Snippet

Bootstrap Image Crop Before Upload · Forms · Plain HTML, CSS & JS · Live preview

What's included

Features

A real cover-fit base scale computed from the image's actual natural dimensions, not a fixed guess
Dragging and zooming are both clamped so the crop frame can never show empty space
The crop is computed at full image resolution via natural-coordinate math, not a downscaled preview
Uses the Pointer Events API (pointerdown/move/up) so the same code handles mouse and touch dragging
Pointer capture ensures a fast drag that leaves the frame's bounds is still tracked correctly

About this UI Snippet

Bootstrap Image Crop Before Upload — HTML, CSS & JavaScript

Screenshot of the Bootstrap Image Crop Before Upload snippet rendered live

The core problem this snippet solves is mapping what's *visually* inside a fixed-size crop frame back to real pixel coordinates in the *original, full-resolution* image — those are two different coordinate systems the moment the image is scaled or panned at all. baseScale starts at whichever ratio makes the image fully cover the 220px frame (Math.max(FRAME / naturalWidth, FRAME / naturalHeight), the same "cover" math CSS's own object-fit: cover uses), and the zoom slider multiplies on top of that base rather than replacing it, so 1.0 on the slider always means "just covering the frame," not "original size."

Dragging updates offsetX/offsetY directly as real pixel positions of the image's top-left corner relative to the frame — not a CSS transform, which would make the reverse-mapping math significantly messier. clampOffsets() runs after every drag and zoom change to guarantee the frame can never show empty space around the image, by constraining each offset between FRAME - scaledSize and 0.

The actual crop happens in Save: since the frame shows a FRAME-pixel window starting at -offsetX, -offsetY in the image's *displayed* pixels, dividing those by the current scale converts them into the image's *natural* pixel coordinates — srcX = -offsetX / scale, srcY = -offsetY / scale, srcSize = FRAME / scale — which is exactly the source rectangle ctx.drawImage() needs to paint precisely what was visible on screen onto the output canvas, at full resolution rather than a blurry re-scale of an already-shrunk preview.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Hand this snippet to an AI coding assistant like Claude and ask it to add a rule-of-thirds grid overlay while dragging for compositional guidance, or to add pinch-to-zoom support on touch devices using two simultaneous pointer events alongside the existing zoom slider.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a Bootstrap 5.3 pan-and-zoom image cropper, using the real Bootstrap CDN framework (bootstrap.min.css and bootstrap.bundle.min.js) for the surrounding UI, with a plain HTML5 canvas for the actual crop output.

Requirements:
- A file input that loads a chosen image into a fixed-size square crop frame (e.g. 220x220px), automatically scaled with a "cover" fit so the image always fully fills the frame with no gaps.
- Dragging inside the frame (using Pointer Events, so it works for both mouse and touch) pans the image, clamped so it can never reveal empty space beyond any edge of the frame.
- A zoom range slider scales the image further while staying properly clamped and centered.
- A "Save crop" button must compute the exact visible region of the frame back into the original image's natural pixel coordinates (accounting for both the current pan offset and zoom scale), and draw that precise region onto an output canvas using drawImage's 9-argument form, so the cropped result is at full resolution rather than a scaled-down copy of the preview.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Choose an image fileIt loads into a circular-adjacent square frame, automatically scaled to fully cover it.
  2. 2
    Drag inside the frameThe image pans under your cursor, clamped so it can never reveal empty space at any edge.
  3. 3
    Move the zoom sliderThe image scales up (and re-clamps its position) while staying centered on wherever you last panned to.
  4. 4
    Click "Save crop"A circular cropped result appears below, computed from the exact visible region at full image resolution.
  5. 5
    Choose a different imageThe editor resets cleanly with fresh scale and centering for the new file.

Real-world uses

Common Use Cases

Profile photo and avatar upload flows
Pairs directly with bootstrap-avatar-upload-editor for a complete photo-selection-to-final-crop pipeline.
Any upload requiring a specific aspect ratio
Ensures a user-selected image fits a fixed square (or, with square math generalized, rectangular) region cleanly before it is ever uploaded.
Learning coordinate-space conversion for canvas
A genuinely useful, complete reference for converting screen/display coordinates back into an image's natural pixel space.

Got questions?

Frequently Asked Questions

The display coordinates are natural-pixel coordinates multiplied by scale; reversing that transform to recover natural coordinates from display coordinates requires dividing by the same scale, not multiplying by it.

Yes — drawImage() always reads from the original, full-resolution <img> element regardless of how small it currently appears on screen, so the crop is never limited by the preview's displayed size the way cropping a screenshot of the frame would be.

Yes. Keep offsetX/offsetY/zoom in component state or refs (refs avoid unnecessary re-renders during a drag), and run the same clamp-and-transform math inside your pointer event handlers.

Give the frame a rectangular size instead of a square one, compute baseScale using that rectangle's width/height against the image's natural dimensions, and adjust srcSize into separate srcWidth/srcHeight values using the frame's actual aspect ratio.