Build an image cropper whose bright selection rectangle can be moved or resized without leaving the 320 × 220 preview. Pointer and keyboard users must get the same geometry and the same live readout.
Implement the React App component in App.tsx. It receives no props. Keep the crop in useState<Rect> and start at { x: 60, y: 40, w: 180, h: 120 }.
pointerdown starts a move. Measure deltas from both the pointer-down coordinate and a snapshot of the starting rect so the box never jumps.pointerdown starts a resize and must not also start a move. The grabbed corner follows the pointer in stage coordinates; its diagonal opposite remains fixed.pointermove and pointerup listeners live on window. Release removes the exact listener references created for that gesture.x: 0..(320 - w) and y: 0..(220 - h). Resize results stay inside the stage, never invert, and keep w >= 40 and h >= 40. Round stored values to whole pixels.role="group", aria-describedby="crop-instructions", and a synchronized label: Crop region, x {x}, y {y}, width {w}, height {h}.button handles named NW resize handle, NE resize handle, SW resize handle, and SE resize handle. Their DOM order is NW, NE, SW, SE.crop-instructions. Keep the readout as role="status" with aria-live="polite"; its exact text is x {x} · y {y} · w {w} · h {h}.x 60 · y 40 · w 180 · h 120.x = 140, because 320 - 180 = 140.{ x: 50, y: 40, w: 190, h: 120 }; the opposite SE point (240, 160) stays fixed.w = 260, the stage's right edge.stageRef.current.getBoundingClientRect() converts viewport pointer coordinates into stage coordinates for resize.resize(rect, corner, px, py) helper for pointer and keyboard input. React state, inline styles, the accessible label, and the readout all derive from the same rect.The full solution is part of Premium
Walkthrough, edge cases, complexity notes, and the runnable editor unlock with a Premium subscription.
Submissions are part of Premium
Unlock community code, comments, reactions, and framework-specific approaches.
Build an image cropper whose bright selection rectangle can be moved or resized without leaving the 320 × 220 preview. Pointer and keyboard users must get the same geometry and the same live readout.
Implement the React App component in App.tsx. It receives no props. Keep the crop in useState<Rect> and start at { x: 60, y: 40, w: 180, h: 120 }.
pointerdown starts a move. Measure deltas from both the pointer-down coordinate and a snapshot of the starting rect so the box never jumps.pointerdown starts a resize and must not also start a move. The grabbed corner follows the pointer in stage coordinates; its diagonal opposite remains fixed.pointermove and pointerup listeners live on window. Release removes the exact listener references created for that gesture.x: 0..(320 - w) and y: 0..(220 - h). Resize results stay inside the stage, never invert, and keep w >= 40 and h >= 40. Round stored values to whole pixels.role="group", aria-describedby="crop-instructions", and a synchronized label: Crop region, x {x}, y {y}, width {w}, height {h}.button handles named NW resize handle, NE resize handle, SW resize handle, and SE resize handle. Their DOM order is NW, NE, SW, SE.crop-instructions. Keep the readout as role="status" with aria-live="polite"; its exact text is x {x} · y {y} · w {w} · h {h}.x 60 · y 40 · w 180 · h 120.x = 140, because 320 - 180 = 140.{ x: 50, y: 40, w: 190, h: 120 }; the opposite SE point (240, 160) stays fixed.w = 260, the stage's right edge.stageRef.current.getBoundingClientRect() converts viewport pointer coordinates into stage coordinates for resize.resize(rect, corner, px, py) helper for pointer and keyboard input. React state, inline styles, the accessible label, and the readout all derive from the same rect.The full solution is part of Premium
Walkthrough, edge cases, complexity notes, and the runnable editor unlock with a Premium subscription.
Submissions are part of Premium
Unlock community code, comments, reactions, and framework-specific approaches.
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.