I'd like to mark a rectangle on an image in a web page for region of interest selection. Meaning, given an image on a web page I'd like to be able to click-drag with a mouse that will draw a rectangle and return a set of x,y,w,h upon mouse release.
How can this be done?
Thanks.
This can be accomplished with a little positioning trick. Set the image in a div positioned relatively. Then you can create an absolute positioned div inside of the div that contains the image as well. This allows you to move the nested div around the front of the image.
As per your update, I have added some javascript that controls the inner div's x,y,w,h on mousedown
, mousemove
and mouseup
NOTE: since this js snippet uses clientX/Y and offsetLeft/Top, the preview below may run a bit off because of its overflow container inside of the post page. I recommend viewing the "Run code snippet" in full screen mode.
var x,y,oldx,oldy;
var showDrag = false;
document.getElementById("cont").addEventListener("mousedown", function(e) {
oldx = e.clientX; //mousedown x coord
oldy = e.clientY; //mouedown y coord
showDrag = true;
e.preventDefault();
});
document.getElementById("cont").addEventListener("mousemove", function(e) {
if (showDrag == true) {
x = e.clientX; //mouseup x coord
y = e.clientY; //mouseup y coord
var bbox = document.getElementById("bbox");
var contbox = document.getElementById("cont");
//get the width and height of the dragged area
var w = (x > oldx ? x-oldx : oldx-x);
var h = (y > oldy ? y-oldy : oldy-y);
var addx = 0, addy = 0;
//these next two lines judge if the box was dragged backward
//and adds the box's width to the bbox positioning offset
if (x < oldx) { addx = w; }
if (y < oldy) { addy = h; }
bbox.style.left = (oldx-parseInt((contbox.offsetLeft+addx)))+"px";
bbox.style.top = (oldy-parseInt((contbox.offsetTop+addy)))+"px";
bbox.style.width = w+"px";
bbox.style.height = h+"px";
bbox.style.display = "block";
}
e.preventDefault();
});
document.getElementById("cont").addEventListener("mouseup", function(e) {
showDrag = false;
e.preventDefault();
});
div.focus-image {
border:1px solid #dddddd;
display:inline-block;
position:relative;
cursor:pointer;
}
div.focus-image div {
display:none;
border:2px solid red;
position:absolute;
left:90px; /*x*/
top:60px; /*y*/
}
<div id="cont" class="focus-image">
<img src="http://cdn.sstatic.net/Sites/stackoverflow/img/[email protected]?v=73d79a89bded&a" />
<div id="bbox"></div>
</div>
My next.js / react + typescript solution (based on Spencer May's answer):
"use client";
import Image from "next/image";
import { useState, MouseEvent } from "react";
type BoxState = {
x: number;
y: number;
width: number;
height: number;
controlled: boolean;
visible: boolean;
};
export default function SelectImage() {
const [box, setBox] = useState<BoxState>({
x: 0,
y: 0,
width: 0,
height: 0,
controlled: false,
visible: false,
});
const [start, setStart] = useState({ x: 0, y: 0 });
function handleMouseDown(e: MouseEvent<HTMLDivElement>) {
const rect = e.currentTarget.getBoundingClientRect();
// Offset relative to parent div
const x1 = e.clientX - rect.left;
const y1 = e.clientY - rect.top;
setStart({ x: x1, y: y1 });
setBox((prev) => ({
...prev,
x: x1,
y: y1,
width: 0,
height: 0,
controlled: true,
visible: true,
}));
}
function handleMouseMove(e: MouseEvent<HTMLDivElement>) {
if (box.controlled) {
const rect = e.currentTarget.getBoundingClientRect();
const x2 = e.clientX - rect.left;
const y2 = e.clientY - rect.top;
setBox((prev) => ({
...prev,
// Handle backward drag
x: Math.min(x2, start.x),
y: Math.min(y2, start.y),
width: Math.abs(x2 - start.x),
height: Math.abs(y2 - start.y),
}));
}
}
function handleMouseUp() {
const cancelSize = 20;
const cancelRect = box.width < cancelSize || box.height < cancelSize;
setBox((prev) => ({ ...prev, controlled: false, visible: !cancelRect }));
}
return (
<div className="flex flex-col justify-center items-center bg-white">
<h2>Select image demo</h2>
<div
className="inline-block relative cursor-pointer border border-gray-300 select-none"
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
draggable="false"
>
<Image
src="http://cdn.sstatic.net/Sites/stackoverflow/img/[email protected]?v=73d79a89bded&a"
className="pointer-events-none select-none"
alt="demo"
width="400"
height="400"
/>
{box.visible && (
<div
className="absolute border-2 border-red-500 pointer-events-none select-none"
style={{
left: box.x,
top: box.y,
width: box.width,
height: box.height,
display: box.visible ? "block" : "none",
}}
></div>
)}
</div>
</div>
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With