Build the crop tool you see on every avatar upload: a bright rectangle floating over a photo that you can drag to reposition and resize from any of its four corners, with a live x / y / w / h readout underneath. The whole thing is one rectangle of state, { x, y, w, h }, and the real work is splitting a pointer gesture into two jobs — move the box, or resize it — while keeping it clamped inside the image.
The starter App.tsx renders the crop box in its resting position over a gradient photo. Make it interactive:
useState({ x: 60, y: 40, w: 180, h: 120 }) and drive the .crop inline left / top / width / height and the readout from it.onPointerDown on the crop body starts a move: remember where the pointer started and the rect started, add pointermove / pointerup listeners on window, and translate x / y by the pointer delta.onPointerDown on a handle starts a resize from that corner. The grabbed corner follows the pointer; the opposite corner stays fixed.0..(320 - w) / 0..(220 - h), and never let it shrink below 40 × 40.180 × 120 crop at (60, 40) and the readout x 60 · y 40 · w 180 · h 120.{ x, y, w, h } — don't track them separately.window, not the handle. A drag has to keep tracking even when the pointer outruns the small box, so the move/up listeners live on window for the duration of the gesture.pointerdown / pointermove / pointerup path covers mouse, touch, and pen; touch-action: none (already in the CSS) stops the browser from scrolling mid-drag.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.