You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas径向菜单单个菜单项Hover效果实现求助

解决Canvas径向菜单单个扇形hover高亮问题

问题出在你没有把Canvas上的扇形区域和鼠标位置做精准匹配,导致hover时整个Canvas触发变化。核心思路是记录每个扇形的角度边界,计算鼠标当前落在哪个扇形范围内,只重绘该扇形的高亮样式。

具体实现步骤

1. 存储扇形角度数据

在组件data中新增两个变量,用来保存每个扇形的角度范围和当前hover的索引:

data() {
  return {
    // ... 原有数据
    sectorData: [], // 存储每个扇形的起始/结束角度
    hoveredIndex: null // 当前hover的扇形索引
  };
}

2. 初始化时记录扇形角度

修改createCanvasBackground方法,将每个扇形的实际角度范围存入sectorData:

createCanvasBackground() {
  this.sectorData = []; // 每次创建前清空
  const canvas = this.$refs.canvas;
  const ctx = canvas.getContext('2d');
  const canvasWidth = 830;
  const canvasHeight = 830;

  canvas.width = canvasWidth;
  canvas.height = canvasHeight;
  const angleStep = (2 * Math.PI) / this.navItems.length;

  for (let i = 0; i < this.navItems.length; i++) {
    const startAngle = i * angleStep - Math.PI / 2;
    const endAngle = (i + 1) * angleStep - Math.PI / 2;
    const adjustedRadius = this.radius + this.spacing;
    const spacingRadians = (this.spacing / (2 * Math.PI * adjustedRadius)) * Math.PI;
    const sectorEndAngle = endAngle - spacingRadians;
    
    // 保存当前扇形的角度范围
    this.sectorData.push({ startAngle, endAngle: sectorEndAngle });

    const radius = Math.min(canvasWidth / 2, canvasHeight / 2);
    ctx.beginPath();
    ctx.moveTo(canvasWidth / 2, canvasHeight / 2);
    ctx.arc(canvasWidth / 2, canvasHeight / 2, radius, startAngle, sectorEndAngle);
    ctx.fillStyle = this.backgroundColor;
    ctx.fill();
  }
}

3. 监听鼠标事件并判断hover扇形

添加鼠标移动和离开的事件监听,计算鼠标位置对应的扇形:

mounted() {
  this.positionNavItems();
  this.createCanvasBackground();
  // 绑定鼠标事件
  const canvas = this.$refs.canvas;
  canvas.addEventListener('mousemove', this.handleCanvasMouseMove);
  canvas.addEventListener('mouseleave', this.handleCanvasMouseLeave);
},
beforeUnmount() {
  // 组件销毁时移除事件监听,避免内存泄漏
  const canvas = this.$refs.canvas;
  canvas.removeEventListener('mousemove', this.handleCanvasMouseMove);
  canvas.removeEventListener('mouseleave', this.handleCanvasMouseLeave);
},
methods: {
  // ... 原有方法
  handleCanvasMouseMove(e) {
    const canvas = this.$refs.canvas;
    const rect = canvas.getBoundingClientRect();
    // 计算鼠标相对于Canvas中心的坐标
    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const mouseX = e.clientX - rect.left - centerX;
    const mouseY = e.clientY - rect.top - centerY;

    // 计算Canvas坐标系下的角度(0朝右,顺时针递增)
    let angle = Math.atan2(mouseY, mouseX);
    // 调整角度至和扇形绘制一致的基准(从顶部开始)
    if (angle < -Math.PI / 2) {
      angle += 2 * Math.PI;
    }

    this.hoveredIndex = null;
    // 遍历所有扇形,判断鼠标角度落在哪个范围内
    for (let i = 0; i < this.sectorData.length; i++) {
      const { startAngle, endAngle } = this.sectorData[i];
      // 处理跨0度的扇形(比如最后一个扇形从接近2π到0附近)
      if (startAngle > endAngle) {
        if (angle >= startAngle || angle <= endAngle) {
          this.hoveredIndex = i;
          break;
        }
      } else {
        if (angle >= startAngle && angle <= endAngle) {
          this.hoveredIndex = i;
          break;
        }
      }
    }
    // 重绘Canvas
    this.redrawCanvas();
  },
  handleCanvasMouseLeave() {
    this.hoveredIndex = null;
    this.redrawCanvas();
  },

4. 重绘Canvas实现高亮

新增redrawCanvas方法,根据当前hover索引重绘对应扇形的颜色:

redrawCanvas() {
  const canvas = this.$refs.canvas;
  const ctx = canvas.getContext('2d');
  const canvasWidth = canvas.width;
  const canvasHeight = canvas.height;

  // 清空画布
  ctx.clearRect(0, 0, canvasWidth, canvasHeight);
  const radius = Math.min(canvasWidth / 2, canvasHeight / 2);

  this.sectorData.forEach((sector, index) => {
    const { startAngle, endAngle } = sector;
    ctx.beginPath();
    ctx.moveTo(canvasWidth / 2, canvasHeight / 2);
    ctx.arc(canvasWidth / 2, canvasHeight / 2, radius, startAngle, endAngle);
    // hover的扇形用高亮色,其他用默认背景色
    ctx.fillStyle = this.hoveredIndex === index ? '#3a3a3a' : this.backgroundColor;
    ctx.fill();
  });
}

5. 可选:同步高亮对应菜单项

如果需要让Canvas扇形和对应的<li>菜单项同步高亮,可以修改<li>的class绑定:

<li v-for="(navItem, index) in navItems" :key="index" 
    :class="['button-' + index, { 'hovered': hoveredIndex === index }]" 
    ref="navItems">

然后添加CSS样式:

.button-inner.hovered {
  /* 菜单项高亮样式,比如改变文字/图标颜色 */
  color: #fff;
}

关键逻辑说明

  • 通过Math.atan2计算鼠标相对于Canvas中心的角度,转换成和扇形绘制一致的坐标系基准
  • 遍历sectorData匹配角度范围,精准定位当前hover的扇形
  • 每次hover变化时重绘Canvas,只修改目标扇形的填充色,避免整个画布不必要的重绘

内容的提问来源于stack exchange,提问作者Roman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 00:14:55