canvas实现饼图悬停需手动计算坐标与扇形命中:1.预存各扇形弧度范围及属性;2.鼠标移动时换算canvas坐标,求极角并归一化,判断是否在扇形角度与半径范围内。

Canvas 本身不支持直接绑定 hover 事件,所有悬停效果都得靠手动坐标计算、扇形命中检测和状态驱动重绘来实现。关键不是“画出来”,而是“知道鼠标在哪块上”并“实时反馈变化”。
1. 预先保存每个扇形的角度范围
绘制饼图前,必须为每一块扇形记录起始角(startAngle)和终止角(endAngle),以及对应的数据标签、颜色等信息。这些角度以弧度为单位,从正x轴逆时针计算。建议用数组存储,例如:
- [{label: 'A', value: 30, start: 0, end: 1.884}, {label: 'B', value: 45, start: 1.884, end: 4.712}, ...]
- 圆心设为 (cx, cy),半径为 r;鼠标坐标转换后得到相对于圆心的偏移 dx = x - cx,dy = y - cy
- 用 Math.atan2(dy, dx) 求出当前鼠标方向的极角(注意:atan2 返回值范围是 [-π, π],需统一转为 [0, 2π))
2. 在 mousemove 中做扇形命中判断
监听 canvas 的 mousemove 事件,每次获取鼠标位置后执行以下逻辑:
- 调用 getBoundingClientRect() 获取 canvas 左上角在视口中的位置,再减去 left/top,换算出鼠标在 canvas 坐标系内的 (x, y)
- 计算到圆心距离:const dist = Math.sqrt(dx * dx + dy * dy),若 dist > r 则直接跳出(不在饼图内)
- 将极角归一化后,遍历扇形数组,检查是否满足:angle >=扇形.start && angle
- 命中则更新 hoveredIndex 或 hoveredItem 状态变量,未命中则清空
3. 用 requestAnimationFrame 驱动高亮重绘
不要在 mousemove 里直接 draw,而应只更新状态,把绘制交给 requestAnimationFrame 调度:
- 重绘函数中先 clearRect 清空画布
- 遍历所有扇形:正常绘制(fillStyle + fillArc);对 hovered 扇形额外加一层描边(strokeStyle + strokeArc)、增大线宽,或叠加浅色半透明覆盖层
- 可配合插值让高亮更平滑:比如 hoveredScale = hovered ? 1.05 : 1,然后在绘制时缩放扇形路径(需用 transform 或重算坐标)
- 如需发光效果,hover 时设置 ctx.shadowBlur = 15,ctx.shadowColor = 'rgba(100, 180, 255, 0.7)',绘制后再恢复基础值
4. 补充 tooltip 和无障碍支持
纯 Canvas 对键盘用户和屏幕阅读器不友好,建议叠加轻量 DOM 层:
- 同级放一个 ,初始 display: none
- 命中时用 JS 设置 innerText 并更新 style.left / style.top(建议偏移鼠标 10px,避免遮挡)
- 给 canvas 添加 tabindex="0",监听 keydown 模拟焦点悬停(如按方向键切换高亮项),同时保持 aria-live="polite" 提示内容变化
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











