canvas不保留图形对象,需自行结构化存储图形数据并实现命中检测;点击坐标须转换为canvas坐标系;每种图形需编写专属检测逻辑;响应应解耦,通过状态驱动重绘。

Canvas本身不保留图形对象,画完即“丢”,所以构建可点击的图形系统,关键不是让Canvas记住图形,而是你自己管理图形数据,并在点击时主动做命中判断。
图形数据必须结构化存储
不能只靠ctx.fillRect()画完就不管——得为每个图形创建描述对象,包含位置、尺寸、类型等必要信息。比如:
- 矩形:
{ type: 'rect', x: 100, y: 150, width: 80, height: 60 } - 圆形:
{ type: 'circle', cx: 200, cy: 200, r: 40 } - 扇形:
{ type: 'sector', cx: 300, cy: 250, r: 50, startAngle: 0, endAngle: Math.PI / 2 }
所有这类对象统一存进一个数组(如 shapes = []),后续点击检测就遍历它。
点击坐标要准确映射到Canvas坐标系
浏览器事件给的是页面坐标(clientX/clientY),而Canvas绘图用的是画布内部坐标。需做两步转换:
- 获取Canvas在页面中的真实位置(用
getBoundingClientRect()) - 减去偏移量,并考虑设备像素比(
window.devicePixelRatio)
简化写法示例:
function getCanvasPoint(canvas, evt) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
return {
x: (evt.clientX - rect.left) * scaleX,
y: (evt.clientY - rect.top) * scaleY
};
}
每种图形写专属命中检测逻辑
Canvas不提供内置“点击哪个图形”的能力,得自己算。常见判断方式:
- 矩形:检查点是否落在
x ≤ px ≤ x+width且y ≤ py ≤ y+height - 圆形:计算点到圆心距离,
Math.hypot(px-cx, py-cy) ≤ r - 扇形:先判是否在圆内,再用
Math.atan2(py-cy, px-cx)算角度,看是否落在起止弧度范围内(注意角度归一化和方向)
多个图形时,建议从后往前遍历(即按绘制顺序的逆序),优先响应最上层图形。
点击响应要解耦,避免直接操作绘图上下文
不要在命中检测里直接调用ctx.fillStyle = 'red'——这会破坏数据与视图分离原则。推荐做法:
- 命中后,修改对应图形对象的
selected = true属性 - 下一帧重绘时,根据
selected状态决定是否加高亮边框或改变填充色 - 所有视觉变化都由统一的
render()函数驱动
这样既便于扩展(比如加多选、拖拽、撤销),也利于调试和测试。
不复杂但容易忽略











