<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>拖拽绘制矩形框 - 可移动可调整大小</title>
<style>
body, html {
margin: 0; padding: 0; height: 100%;
user-select: none;
}
#container {
position: relative;
width: 100vw;
height: 100vh;
background-color: #f0f0f0;
border: 1px solid #ccc;
overflow: hidden;
}
.rectangle {
position: absolute;
border: 2px solid;
background-color: rgba(0, 123, 255, 0.2);
cursor: move;
box-sizing: border-box;
}
.delete-btn {
position: absolute;
top: -8px;
right: -8px;
width: 16px;
height: 16px;
background: #ff4d4f;
color: white;
font-weight: bold;
font-size: 12px;
line-height: 16px;
text-align: center;
border-radius: 50%;
cursor: pointer;
user-select: none;
z-index: 1000;
pointer-events: auto;
}
.resize-handle {
position: absolute;
width: 10px;
height: 10px;
background: white;
border: 2px solid #007bff;
box-sizing: border-box;
z-index: 10;
}
.resize-handle.nw { top: -6px; left: -6px; cursor: nwse-resize; }
.resize-handle.ne { top: -6px; right: -6px; cursor: nesw-resize; }
.resize-handle.sw { bottom: -6px; left: -6px; cursor: nesw-resize; }
.resize-handle.se { bottom: -6px; right: -6px; cursor: nwse-resize; }
#info {
position: fixed;
bottom: 10px;
left: 10px;
max-height: 150px;
overflow-y: auto;
background: rgba(255,255,255,0.9);
border: 1px solid #ccc;
padding: 8px;
font-family: monospace;
font-size: 14px;
width: 320px;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="info"><strong>绘制的矩形框信息:</strong><br/></div>
<script>
(function() {
const container = document.getElementById('container');
const info = document.getElementById('info');
let startX = 0;
let startY = 0;
let currentRect = null;
let rectangles = [];
let dragData = null; // {rect, startX, startY, origX, origY}
let resizeData = null; // {rect, handle, startX, startY, origX, origY, origW, origH}
const colors = [
'#007bff', '#28a745', '#dc3545', '#ffc107', '#17a2b8',
'#6f42c1', '#e83e8c', '#fd7e14', '#20c997', '#6610f2'
];
function getRelativePosition(clientX, clientY) {
const rect = container.getBoundingClientRect();
return {
x: clientX - rect.left,
y: clientY - rect.top
};
}
function updateInfoLine(rect) {
if (!rect._infoLine) return;
const x = parseFloat(rect.style.left);
const y = parseFloat(rect.style.top);
const w = parseFloat(rect.style.width);
const h = parseFloat(rect.style.height);
rect._infoLine.textContent = `矩形框 ${rectangles.indexOf(rect) + 1}: x=${Math.round(x)}, y=${Math.round(y)}, 宽=${Math.round(w)}, 高=${Math.round(h)}`;
}
function createResizeHandle(position) {
const handle = document.createElement('div');
handle.className = 'resize-handle ' + position;
return handle;
}
container.addEventListener('mousedown', (e) => {
if (e.button !== 0) return; // only left mouse button
const target = e.target;
// 如果点击的是删除按钮
if (target.classList.contains('delete-btn')) {
return; // 由其自身的监听器处理
}
// 如果点击的是调整大小的控制点
if (target.classList.contains('resize-handle')) {
const rect = target.parentElement;
const pos = getRelativePosition(e.clientX, e.clientY);
resizeData = {
rect,
handle: target.classList[1], // nw, ne, sw, se
startX: pos.x,
startY: pos.y,
origX: parseFloat(rect.style.left),
origY: parseFloat(rect.style.top),
origW: parseFloat(rect.style.width),
origH: parseFloat(rect.style.height)
};
e.preventDefault();
return;
}
// 如果点击的是已有的矩形(非删除或调整大小)
if (target.classList.contains('rectangle')) {
const rect = target;
const pos = getRelativePosition(e.clientX, e.clientY);
dragData = {
rect,
startX: pos.x,
startY: pos.y,
origX: parseFloat(rect.style.left),
origY: parseFloat(rect.style.top)
};
e.preventDefault();
return;
}
// 否则,开始绘制新的矩形
const pos = getRelativePosition(e.clientX, e.clientY);
startX = pos.x;
startY = pos.y;
currentRect = document.createElement('div');
currentRect.className = 'rectangle';
// 颜色循环分配
const color = colors[rectangles.length % colors.length];
currentRect.style.borderColor = color;
currentRect.style.backgroundColor = color + '33'; // 20% 透明度
currentRect.style.left = startX + 'px';
currentRect.style.top = startY + 'px';
currentRect.style.width = '0px';
currentRect.style.height = '0px';
container.appendChild(currentRect);
rectangles.push(currentRect);
// 创建信息行元素
const infoLine = document.createElement('div');
infoLine.style.display = 'flex';
infoLine.style.alignItems = 'center';
infoLine.style.marginBottom = '4px';
// 创建文本节点,显示矩形信息
const infoText = document.createElement('span');
infoText.textContent = `矩形框 ${rectangles.length}: x=0, y=0, 宽=0, 高=0`;
infoLine.appendChild(infoText);
// 创建删除按钮
const infoDeleteBtn = document.createElement('button');
infoDeleteBtn.textContent = '删除';
infoDeleteBtn.style.marginLeft = '10px';
infoDeleteBtn.style.cursor = 'pointer';
infoDeleteBtn.style.padding = '2px 6px';
infoDeleteBtn.style.border = '1px solid #ccc';
infoDeleteBtn.style.backgroundColor = '#f8f9fa';
infoDeleteBtn.style.borderRadius = '4px';
infoDeleteBtn.style.fontSize = '12px';
infoDeleteBtn.style.color = '#333';
infoDeleteBtn.style.userSelect = 'none';
// 删除按钮点击事件,删除对应矩形和信息行
(function(rect, line) {
infoDeleteBtn.addEventListener('click', function() {
// 删除对应矩形元素
if (container.contains(rect)) {
container.removeChild(rect);
}
// 删除对应信息行
if (info.contains(line)) {
info.removeChild(line);
}
// 从数组中移除对应矩形
const index = rectangles.indexOf(rect);
if (index > -1) {
rectangles.splice(index, 1);
}
});
})(currentRect, infoLine);
// 将删除按钮添加到信息行
infoLine.appendChild(infoDeleteBtn);
// 将信息行添加到info面板
info.appendChild(infoLine);
// 存储引用以便后续更新和删除
currentRect._infoLine = infoText;
e.preventDefault();
});
container.addEventListener('mousemove', (e) => {
const pos = getRelativePosition(e.clientX, e.clientY);
// 处理调整大小
if (resizeData) {
const {rect, handle, startX, startY, origX, origY, origW, origH} = resizeData;
let newX = origX;
let newY = origY;
let newW = origW;
let newH = origH;
const dx = pos.x - startX;
const dy = pos.y - startY;
switch (handle) {
case 'nw':
newX = origX + dx;
newY = origY + dy;
newW = origW - dx;
newH = origH - dy;
break;
case 'ne':
newY = origY + dy;
newW = origW + dx;
newH = origH - dy;
break;
case 'sw':
newX = origX + dx;
newW = origW - dx;
newH = origH + dy;
break;
case 'se':
newW = origW + dx;
newH = origH + dy;
break;
}
// 防止宽高为负
if (newW < 10) newW = 10;
if (newH < 10) newH = 10;
if (newX < 0) newX = 0;
if (newY < 0) newY = 0;
if (newX + newW > container.clientWidth) newX = container.clientWidth - newW;
if (newY + newH > container.clientHeight) newY = container.clientHeight - newH;
rect.style.left = newX + 'px';
rect.style.top = newY + 'px';
rect.style.width = newW + 'px';
rect.style.height = newH + 'px';
updateInfoLine(rect);
e.preventDefault();
return;
}
// 处理拖拽
if (dragData) {
const {rect, startX, startY, origX, origY} = dragData;
let newX = origX + (pos.x - startX);
let newY = origY + (pos.y - startY);
// 保持在容器内
if (newX < 0) newX = 0;
if (newY < 0) newY = 0;
if (newX + rect.clientWidth > container.clientWidth) newX = container.clientWidth - rect.clientWidth;
if (newY + rect.clientHeight > container.clientHeight) newY = container.clientHeight - rect.clientHeight;
rect.style.left = newX + 'px';
rect.style.top = newY + 'px';
updateInfoLine(rect);
e.preventDefault();
return;
}
// 处理绘制新矩形
if (!currentRect) return;
const x = Math.min(pos.x, startX);
const y = Math.min(pos.y, startY);
const width = Math.abs(pos.x - startX);
const height = Math.abs(pos.y - startY);
currentRect.style.left = x + 'px';
currentRect.style.top = y + 'px';
currentRect.style.width = width + 'px';
currentRect.style.height = height + 'px';
updateInfoLine(currentRect);
e.preventDefault();
});
container.addEventListener('mouseup', (e) => {
if (currentRect) {
currentRect = null;
e.preventDefault();
}
if (dragData) {
dragData = null;
e.preventDefault();
}
if (resizeData) {
resizeData = null;
e.preventDefault();
}
});
container.addEventListener('mouseleave', (e) => {
if (currentRect) {
currentRect = null;
}
if (dragData) {
dragData = null;
}
if (resizeData) {
resizeData = null;
}
});
// 监听容器子元素变化,给新矩形添加调整大小控制点
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.classList && node.classList.contains('rectangle')) {
['nw', 'ne', 'sw', 'se'].forEach(pos => {
const handle = createResizeHandle(pos);
node.appendChild(handle);
});
}
});
});
});
observer.observe(container, { childList: true });
})();
</script>
</body>
</html>