How to Build a Canvas‑Based Image Cropping Hook in React

This article explains how to create a reusable React hook that uses two canvas layers to let users select a region on an image, shows a gray‑out overlay with a border during selection, and returns the cropped area as a base64 PNG, including full implementation details and future enhancements.

ByteFE
ByteFE
ByteFE
How to Build a Canvas‑Based Image Cropping Hook in React

Implementation Overview

This article describes a React hook that enables interactive image cropping using two HTML5 canvas elements. Canvas A renders the cropping overlay (gray mask and selection border) while Canvas B holds the original image for pixel extraction. The hook returns four items: init (initialises the canvases inside a container), cut (starts the cropping process with a source image URL), cancelCut (clears state and event listeners), and clipImgData (a Base64‑encoded PNG of the cropped region).

Core Steps

Initialisation ```javascript const init = (wrap) => { if (!wrap) return; clipAreaWrap.current = wrap; clipCanvas.current = document.createElement('canvas'); drawCanvas.current = document.createElement('canvas'); clipCanvas.current.style = 'width:100%;height:100%;z-index:2;position:absolute;left:0;top:0;'; drawCanvas.current.style = 'width:100%;height:100%;z-index:1;position:absolute;left:0;top:0;'; wrap.appendChild(clipCanvas.current); wrap.appendChild(drawCanvas.current); }; ```

Start Cropping When cut(sourceImg) is called, the source image is loaded into an img element, appended to the container (so CSS size is applied), and drawn onto Canvas B . The hook also configures the gray mask and border colours for Canvas A : ```javascript clipCtx.fillStyle = 'rgba(0,0,0,0.6)'; // gray overlay clipCtx.strokeStyle = 'rgba(0,143,255,1)'; // border colour ```

Mouse Interaction Three mouse events on Canvas A drive the selection rectangle: mousedown records the start point {x, y}. mousemove (while a start point exists) calls fill() to redraw the overlay with the current rectangle. mouseup (registered on document to capture leaving the canvas) finalises the rectangle, extracts the cropped image, and resets the start point.

```javascript clipCanvas.current.onmousedown = (e) => { start = { x: e.offsetX, y: e.offsetY }; }; clipCanvas.current.onmousemove = (e) => { if (start) { fill(clipCtx, wrapWidth, wrapHeight, start.x, start.y, e.offsetX - start.x, e.offsetY - start.y); } }; document.addEventListener('mouseup', (e) => { if (start) { const url = getClipPicUrl({ x: start.x, y: start.y, w: e.offsetX - start.x, h: e.offsetY - start.y }, drawCtx); start = null; setClipImgData(url); } }); ```

Overlay Rendering The fill() function clears the previous overlay and draws three layers using globalCompositeOperation :

context.clearRect(0, 0, ctxWidth, ctxHeight);
context.beginPath();
// 1. Gray mask
context.globalCompositeOperation = 'source-over';
context.fillRect(0, 0, ctxWidth, ctxHeight);
// 2. Transparent selection rectangle
context.globalCompositeOperation = 'destination-out';
context.fillRect(x, y, w, h);
// 3. Border
context.globalCompositeOperation = 'source-over';
context.moveTo(x, y);
context.lineTo(x + w, y);
context.lineTo(x + w, y + h);
context.lineTo(x, y + h);
context.lineTo(x, y);
// context.stroke(); // optional visual border
context.closePath();

Extracting the Cropped Image After mouse release, the selected region is copied from Canvas B using getImageData , drawn onto a temporary canvas, and converted to a Base64 PNG:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const data = drawCtx.getImageData(area.x, area.y, area.w, area.h);
canvas.width = area.w;
canvas.height = area.h;
ctx.putImageData(data, 0, 0);
return canvas.toDataURL('image/png', 1);

Hook API

init(containerElement)

– creates and inserts the two canvases into the provided DOM element. cut(imageUrl) – loads the image, draws it onto the background canvas, and activates mouse listeners. cancelCut() – clears both canvases, removes mouse handlers, and resets dimensions. clipImgData – a React state value containing the Base64 string of the last cropped image.

CSS (required for layout)

.clip-area-wrap {
  height: 450px;
  position: relative;
}
.clip-area-wrap img {
  width: 100%;
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  max-width: 100%;
  max-height: 100%;
}
.clip-img-area {
  width: 250px;
  height: 250px;
  position: relative;
  margin: 0 auto;
}
.clip-img-area img {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-width: 100%;
  max-height: 100%;
}

Reference

Canvas implementation of image cropping – https://blog.csdn.net/HuangsTing/article/details/106141263

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendJavaScriptReactCanvasHookImage Cropping
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.