Canvas交互核心是坐标映射与几何判定:先校准鼠标坐标(getBoundingClientRect+缩放/DPR还原),再解耦绘图坐标与业务坐标(线性/非线性映射),多图层需坐标穿透与自顶向下命中检测,区域交互用射线法或绕数法判定点在多边形内。

Canvas 交互的核心不是“监听点击”,而是“把鼠标点的位置,准确换算成画布里某个图形该响应的位置”。它不靠 DOM 事件冒泡,全靠手动坐标映射 + 几何判定。
鼠标坐标 → Canvas 像素坐标的校准
直接用 event.clientX / clientY 会出错:它受 margin、border、缩放、高清屏影响。必须走标准校准流程:
- 调用 canvas.getBoundingClientRect() 获取画布左上角在视口中的真实像素位置
- 计算相对偏移:x = event.clientX - rect.left,y = event.clientY - rect.top
- 若 canvas 被 CSS 缩放(比如 width: 300px; 但 canvas.width=600),需按比例还原:realX = x * (canvas.width / rect.width)
- 对高清屏(devicePixelRatio > 1),若已用 DPR 设置了 canvas.width/height,此步已隐含;否则还需乘以 DPR
Canvas 坐标系与业务逻辑坐标的解耦
绘图坐标(0,0 在左上)往往和业务需求不一致——比如地图要经纬度、图表要数值轴、排版要印刷坐标。不能硬套,得做映射层:
- 定义数据范围:如温度图表中 y ∈ [0, 100],对应画布像素 y ∈ [canvas.height - 50, 50]
- 写线性映射函数:pixelY = topMargin + (maxVal - value) * scale(注意 y 向下增,所以用 maxVal - value)
- 反向映射(如点击查值):value = maxVal - (pixelY - topMargin) / scale
- 非线性场景(如长图聚焦)则用分段函数或查表,而非简单比例
多图层下的坐标穿透与命中检测
当有多个 Canvas 叠加(如 Photoshop 式图层),鼠标事件只触发在最上层 DOM 元素,但操作可能作用于任意图层:
立即学习“前端免费学习笔记(深入)”;
- 统一监听主画布的 mousemove / click,不分散绑定
- 将 client 坐标先映射到主画布像素坐标,再减去目标图层自身的 left/top 偏移(若用 absolute 定位),得到该图层内局部坐标
- 命中检测必须从顶层图层往下遍历,一旦某图层返回 true 就停止——保证上层图形优先响应
- 图层锁定或仅编辑当前层时,先判断 activeLayer === targetLayer,再执行绘制或 hit test
区域交互:点在多边形内的判定
地图、流程图、SVG 替代方案等依赖“点击某区域”时,本质是数学问题:
- 每个可交互区域存为顶点数组,如 [[x1,y1], [x2,y2], …],坐标已是映射后的画布像素
- 用射线法(Ray Casting)判断点击点是否在多边形内:向右发射水平射线,统计与边的交点数,奇数即命中
- 避免逐帧重算:预生成包围盒(bounding box),先快速排除明显不相交的区域
- 支持洞(如省份含岛屿)需区分外环与内环,用 winding number 法更稳妥



















