圆心偏移需显式定义逻辑圆心并统一使用,刻度角度须将数值线性映射到弧度区间,文本标注要设置textAlign和textBaseline对齐,指针旋转应每次实时重算角度。

Canvas 绘制可交互的仪表盘时,圆心偏移和刻度计算是两个直接影响视觉准确性和交互逻辑的关键环节。处理不当容易导致指针错位、刻度不均、数值映射失真等问题。
圆心偏移需统一坐标系基准
仪表盘常被设计为非居中布局(比如顶部留白、侧边控件占用),此时画布的物理中心(canvas.width/2, canvas.height/2)≠ 逻辑圆心。必须显式定义逻辑圆心,并在所有绘制操作中一致使用:
- 显式声明圆心变量,避免重复计算
const gauge = { x: 200, // 可自由设定,不一定是 canvas 宽高一半 y: 150, radius: 120 }; - 所有
arc()、lineTo()、fillText()等调用,都以gauge.x/gauge.y为原点计算坐标 - 若启用
devicePixelRatio缩放(如移动端高清屏),缩放后需同步调整圆心坐标:const dpr = window.devicePixelRatio || 1; ctx.scale(dpr, dpr); // 圆心坐标保持不变(逻辑像素),但绘图时自动适配物理像素
刻度角度要按弧度线性映射
刻度不是等间距画线,而是将数值范围(如 0–100)线性映射到一段弧度区间(如 −90° → +90°,即 −Math.PI/2 → Math.PI/2):
- 先确定仪表盘的起始角
startRad和终止角endRad(顺时针方向) - 计算总跨度:
spanRad = endRad - startRad - 每个刻度对应的角度增量:
stepRad = spanRad / (tickCount - 1) - 第
i个刻度的角度:angle = startRad + i * stepRad
例如,绘制 11 个主刻度(0 到 100,步长 10):
const startRad = -Math.PI / 2; // 270° 方向(正上方)
const endRad = Math.PI / 2; // 90° 方向(正下方)
const spanRad = endRad - startRad; // π 弧度(半圆)
for (let i = 0; i <= 10; i++) {
const ratio = i / 10;
const angle = startRad + ratio * spanRad;
// 计算刻度线端点(内圈 & 外圈)
const innerX = gauge.x + Math.cos(angle) * (gauge.radius - 20);
const innerY = gauge.y + Math.sin(angle) * (gauge.radius - 20);
const outerX = gauge.x + Math.cos(angle) * (gauge.radius - 5);
const outerY = gauge.y + Math.sin(angle) * (gauge.radius - 5);
ctx.moveTo(innerX, innerY);
ctx.lineTo(outerX, outerY);
}文本标注要配合 textAlign 与 textBaseline
刻度数字若直接用 fillText(text, x, y) 定位,极易因字体基线偏移而上下晃动。务必提前设置对齐方式:
ctx.textAlign = 'center'; // 水平居中对齐
ctx.textBaseline = 'middle'; // 垂直居中对齐
// 此时 fillText('50', gauge.x, gauge.y) 就真正落在圆心
// 同理,刻度文字也应以该角度下的圆上点为中心绘制:
const textX = gauge.x + Math.cos(angle) * (gauge.radius + 15);
const textY = gauge.y + Math.sin(angle) * (gauge.radius + 15);
ctx.fillText(`${i * 10}`, textX, textY);交互指针旋转依赖角度实时重算
用户输入或数据更新时,不要缓存角度值,而应每次从当前数值重新计算:
function updatePointer(value, min = 0, max = 100) {
const ratio = Math.max(0, Math.min(1, (value - min) / (max - min)));
const angle = startRad + ratio * spanRad;
// 清除旧指针(或用 requestAnimationFrame 重绘整帧)
drawPointer(angle);
}这样能确保数值变化与视觉旋转严格同步,避免累积误差。


















