3. 导航箭头动态指示

导航箭头动态指示 学习卡片

任务目标

在 HUD 画布上根据模拟导航数据绘制转向箭头,并让箭头随转向角度平滑过渡。核心是:导航数据 → 目标角度 → 缓动插值 → 每帧重绘。

实现思路

导航数据 {angle, dist} 目标角度 targetAngle 缓动插值 lerp 每帧逼近 重绘箭头 requestAnimationFrame

导航数据每 1.5 秒更新一次目标角度,渲染循环用 requestAnimationFrame 以固定缓动系数把当前角度逼近目标角度,实现平滑转向动画。

完整代码

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>HUD 导航箭头</title>
<style>
  body { margin:0; background:#111; display:flex; justify-content:center; align-items:center; height:100vh; }
  canvas { background:#0a0a1a; border-radius:12px; }
</style></head>
<body>
<canvas id="hud" width="480" height="480"></canvas>
<script>
const canvas = document.getElementById('hud');
const ctx = canvas.getContext('2d');
const CX = canvas.width / 2, CY = canvas.height / 2;

// 模拟导航数据:角度(度, 0=正上方, 顺时针) 与剩余距离(米)
const navData = [
  { angle: 0,   dist: 200 },
  { angle: 45,  dist: 150 },
  { angle: 90,  dist: 100 },
  { angle: -60, dist: 80  },
  { angle: 0,   dist: 30  }
];

let targetAngle = navData[0].angle;   // 目标角度
let currentAngle = navData[0].angle;  // 当前渲染角度
let dist = navData[0].dist;
let idx = 0;

// 每 1.5 秒切换一条导航数据
setInterval(() => {
  idx = (idx + 1) % navData.length;
  targetAngle = navData[idx].angle;
  dist = navData[idx].dist;
}, 1500);

// 角度缓动:处理 -180~180 的最短路径
function lerpAngle(cur, target, t) {
  let diff = ((target - cur + 540) % 360) - 180;
  return cur + diff * t;
}

function drawArrow(angleDeg) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 外圈刻度
  ctx.strokeStyle = '#2a2a4a';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.arc(CX, CY, 180, 0, Math.PI * 2);
  ctx.stroke();

  // 剩余距离文字
  ctx.fillStyle = '#7ec8ff';
  ctx.font = '20px sans-serif';
  ctx.textAlign = 'center';
  ctx.fillText(dist + ' m', CX, CY + 120);

  // 旋转箭头
  ctx.save();
  ctx.translate(CX, CY);
  ctx.rotate(angleDeg * Math.PI / 180);

  ctx.fillStyle = '#00e5ff';
  ctx.shadowColor = '#00e5ff';
  ctx.shadowBlur = 18;
  ctx.beginPath();
  ctx.moveTo(0, -110);   // 箭头尖端
  ctx.lineTo(-45, 20);
  ctx.lineTo(-15, 20);
  ctx.lineTo(-15, 70);
  ctx.lineTo(15, 70);
  ctx.lineTo(15, 20);
  ctx.lineTo(45, 20);
  ctx.closePath();
  ctx.fill();

  ctx.restore();
}

// 渲染循环:每帧向目标角度逼近 12%
function loop() {
  currentAngle = lerpAngle(currentAngle, targetAngle, 0.12);
  drawArrow(currentAngle);
  requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>

运行与验证

  1. 将代码保存为 hud_arrow.html,用浏览器直接打开。
  2. 观察画布中央的青色箭头:每 1.5 秒目标角度切换一次,箭头应平滑旋转到新方向,而非瞬间跳变。
  3. 箭头下方距离数字随导航数据同步更新。
  4. 通过条件:箭头旋转过程连续无跳变;角度从 90° 切到 -60° 时走最短路径(顺时针 150° 而非逆时针 210°)。

关键点

  • lerpAngle 用 ((target - cur + 540) % 360) - 180 把角度差归一化到 -180~180,避免绕远路。
  • 缓动系数 0.12 决定动画速度:越大越快,越小越平滑。可调此值观察效果。
  • 真实 HUD 中把 navData 替换为 CAN/以太网解析出的导航信号即可,渲染逻辑不变。