用实现鼠标拖拽画矩形需监听mousedown、mousemove、mouseup事件,通过getBoundingClientRect()校准坐标,用beginPath()+rect()+stroke()绘制虚线半透明临时框,mouseup后存入坐标数组;标注框可拖动缩放删除需hit-test判断交互状态,统一canvas绘制;数据以JSON保存坐标与元信息,注意缩放还原;CSS方案在缩放、旋转、多边形等场景下精度与扩展性不足,canvas是更优选择。

怎么用 捕获鼠标拖拽画矩形
核心是监听 mousedown、mousemove、mouseup 三个事件,自己算坐标,别依赖第三方库的“标注模式”开关。Canvas 坐标要减去元素 offset(特别是带滚动或缩放时),否则框会偏移。
常见错误:直接用 event.clientX/Y 当作 canvas 像素坐标 —— 必须通过 getBoundingClientRect() 校准:
const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top;
画框时用 beginPath() + rect() + stroke(),别用 fillRect()(不支持边框样式和透明度)。
- 拖拽中实时重绘:先
clearRect(0, 0, width, height),再重画所有已有标注 + 当前临时框 - 临时框建议用虚线(
setLineDash([4, 2]))+ 半透明色(strokeStyle = 'rgba(0,120,255,0.7)') - mouseup 后把当前框坐标存进数组,比如
{x, y, width, height, id: Date.now()}
怎么让标注框可拖动、缩放、删除
不是给每个框建一个 DOM 元素,而是统一在 canvas 上绘制并响应事件。关键在于 hit-test:鼠标点下时,遍历所有标注框,判断是否落在其内(用 x 等条件),命中后进入编辑态。
编辑态分三种操作,靠 mousemove 距离和方向判断:
- 靠近框边缘(如
Math.abs(mouse_x - (x + width)) )→ 缩放(只改 width/height) - 靠近中心区域 → 拖动(整体平移 x/y)
- 靠近右下角小热区(比如宽高 12px 的正方形)→ 删除(从数组 splice 掉)
注意:缩放时要限制最小尺寸(如 width > 8 && height > 8),否则 canvas 渲染会出错;拖动时记得实时更新所有相关坐标,别漏掉关联数据。
怎么保存/加载标注数据到 JSON
标注数据本质就是一组坐标 + 元数据,不需要存图片二进制。标准结构推荐:
{
"imageSrc": "cat.jpg",
"annotations": [
{ "id": 1698765432, "x": 42, "y": 87, "width": 120, "height": 95, "label": "cat_face" },
{ "id": 1698765433, "x": 210, "y": 134, "width": 88, "height": 62, "label": "left_eye" }
]
}
保存用 JSON.stringify() + localStorage.setItem('anno_cat.jpg', json) 最快;加载时注意校验字段是否存在(比如旧数据没 label 字段,代码里得 fallback)。
- 文件名做 key 是简单做法,但要注意 URL 编码(
encodeURIComponent(src))避免特殊字符出错 - 如果图片有缩放,保存前要把坐标转回原始尺寸(比如 canvas 显示为 50%,则存
x * 2) - 不要在 JSON 里存函数或 canvas 对象 —— 序列化会丢
为什么不用 ![]()
+ CSS 定位做标注
纯 CSS 方案(比如绝对定位 Canvas 是唯一能兼顾精度、性能和扩展性的选择。哪怕只做矩形标注,也建议从 canvas 打底 —— 后续加语义分割掩码、关键点连线,都不用推翻重写。
transform: scale() 或 width: 100% 会让子元素定位完全失准,CSS calc 难以对齐imageSmoothingEnabled = false 控制像素风)touchstart/touchmove 并 preventDefault)
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











