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

Canvas圆弧点击监听实现:带进度百分比的双色圆弧点击处理

嘿,这个问题挺常见的——Canvas本身是位图画布,不像DOM元素自带事件监听,所以得手动计算点击位置是否落在目标弧段里。我给你一套完整的实现方案,包括绘制带进度的圆弧,还有核心的点击检测逻辑:

实现Canvas圆弧进度条及区域点击监听

1. 基础配置与绘制圆弧

首先我们得定义圆弧的核心参数,然后实现绘制逻辑,包括蓝色剩余区域、绿色进度区域,还有中间的百分比文本。

HTML结构

<canvas id="progressArc" width="300" height="300"></canvas>

初始化与绘制代码

const canvas = document.getElementById('progressArc');
const ctx = canvas.getContext('2d');

// 核心配置参数,可按需调整
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 100; // 圆弧半径
const lineWidth = 20; // 圆弧宽度
const progress = 0.6; // 进度比例(60%)

// 角度转换:Canvas使用弧度,0指向右侧,顺时针为正方向
const startAngle = -Math.PI / 2; // 让圆弧从顶部开始绘制
const progressAngle = startAngle + 2 * Math.PI * progress; // 进度结束角度
const endAngle = startAngle + 2 * Math.PI; // 完整圆弧的结束角度

// 绘制圆弧的函数
function drawArc() {
  // 清空画布
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // 绘制蓝色剩余区域
  ctx.beginPath();
  ctx.arc(centerX, centerY, radius, progressAngle, endAngle);
  ctx.strokeStyle = '#3498db';
  ctx.lineWidth = lineWidth;
  ctx.stroke();
  
  // 绘制绿色进度区域
  ctx.beginPath();
  ctx.arc(centerX, centerY, radius, startAngle, progressAngle);
  ctx.strokeStyle = '#2ecc71';
  ctx.lineWidth = lineWidth;
  ctx.stroke();
  
  // 绘制中间的进度百分比文本
  ctx.fillStyle = '#333';
  ctx.font = '24px Arial';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(`${Math.round(progress * 100)}%`, centerX, centerY);
}

// 首次绘制
drawArc();

2. 核心:点击区域检测逻辑

因为Canvas没有内置的元素点击检测,我们得自己实现一个函数,判断点击点是否落在指定的弧段内。这个函数要做两件事:

  • 判断点击点到圆心的距离是否在圆弧的宽度范围内(因为是描边圆弧,所以是环形区域)
  • 判断点击点对应的角度是否落在目标弧段的角度区间内
function isPointInArc(pointX, pointY, arcStartAngle, arcEndAngle) {
  // 计算点击点相对于圆心的坐标
  const dx = pointX - centerX;
  const dy = pointY - centerY;
  
  // 计算点到圆心的直线距离
  const distance = Math.sqrt(dx * dx + dy * dy);
  
  // 判断是否在圆弧的宽度范围内(环形区域)
  const minDistance = radius - lineWidth / 2;
  const maxDistance = radius + lineWidth / 2;
  if (distance < minDistance || distance > maxDistance) {
    return false;
  }
  
  // 计算点击点对应的弧度(返回范围:-π 到 π)
  let angle = Math.atan2(dy, dx);
  
  // 转换为 0 到 2π 的范围,方便和圆弧角度做比较
  if (angle < 0) {
    angle += 2 * Math.PI;
  }
  
  // 把弧段的起始、结束角度也统一转换为 0 到 2π 的范围
  const normalizedStart = arcStartAngle < 0 ? arcStartAngle + 2 * Math.PI : arcStartAngle;
  const normalizedEnd = arcEndAngle < 0 ? arcEndAngle + 2 * Math.PI : arcEndAngle;
  
  // 判断角度是否在弧段范围内,还要处理弧段跨0点的情况(比如从底部到顶部的圆弧)
  if (normalizedStart > normalizedEnd) {
    return angle >= normalizedStart || angle <= normalizedEnd;
  } else {
    return angle >= normalizedStart && angle <= normalizedEnd;
  }
}

3. 绑定点击事件

最后给Canvas绑定点击事件,转换点击坐标(避免Canvas样式偏移导致坐标错误),然后调用上面的检测函数判断点击区域:

canvas.addEventListener('click', (e) => {
  // 获取Canvas在页面中的位置,转换点击坐标为Canvas内部坐标
  const rect = canvas.getBoundingClientRect();
  const clickX = e.clientX - rect.left;
  const clickY = e.clientY - rect.top;
  
  // 判断是否点击绿色进度区域
  if (isPointInArc(clickX, clickY, startAngle, progressAngle)) {
    alert('点击了绿色进度区域!');
    // 这里替换成你的业务逻辑,比如跳转、修改进度等
  } 
  // 判断是否点击蓝色剩余区域
  else if (isPointInArc(clickX, clickY, progressAngle, endAngle)) {
    alert('点击了蓝色剩余区域!');
    // 这里替换成你的业务逻辑
  }
});

关键注意事项

  • 角度统一转换:Canvas的角度系统和我们日常认知的有点差异,一定要把所有角度统一转换为0到2π的范围,避免跨0点的弧段检测出错。
  • 坐标转换:如果Canvas通过CSS设置了和自身width/height不同的尺寸,必须用getBoundingClientRect()转换点击坐标,否则会出现点击位置偏移的问题。
  • 圆弧宽度:因为是描边圆弧,点击区域是环形,所以要判断距离是否在半径±线宽/2的范围内,而不是刚好等于半径。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:40:17