2026-09-02 真机验证通过:平面视图跟随朝向与官方 App 手感一致。本笔记是「错误方案 → 源码级根因 → 最终优雅修法」的完整教训,替代早期「插帧」思路(见文末关联的废弃笔记)。

原先是怎样写的(两个都失败)

失败方案 A:每次回调直接 map.setHeading(target)
指南针桥每约 80ms 推一次 receiveNativeHeading → applyNavigationHeading → 直接 map.setHeading(target)。

失败方案 B:JS 层 96–144ms smoothstep 插帧
为"缓解"卡顿,在 rAF 里把一次转向拆成 6~8 个中间角度,逐帧 setHeading。

两者的共同症状:设备转过去了,地图要等 1~2 秒才开始动,且永远是连续缓动动画;插帧后更碎。调试面板里 cameraHeading(期望值)立即正确,说明 JS 计算无误——问题全在 setHeading 这条通道。

根因(扒 BMapGL 压缩源码确认)

抓取 https://api.map.baidu.com/getscript?v=1.0&type=webgl&ak=<AK> 的 GL 库源码,找到 Map#setHeading / Map#setTilt 真实实现:

setHeading:function(T,i){ i=i||{};
  ...
  if(i.noAnimation){ this._setValue("heading",e); i.callback&&i.callback(); return }  // 瞬移分支
  this._animationInfo.heading = { current:C, diff:e-C };
  this._startAnimation(i)                                  // 必走动画分支
}
_startAnimation:function(i){ ...
  var mW = i.duration || 500;                              // 默认 500ms 缓动
  if(C._ani){ C._ani.stop(...) }                           // 每次先打断上一个动画
  ...
}

关键事实:

  • setHeading / setTilt 是 BMapGL 非公开方法(官方 WebGL 1.0 参考文档无条目,公开的只有 startViewAnimation(ViewAnimation) 关键帧动画类)。
  • 不带 noAnimation 时,每次调用都会启动一个默认 500ms、带缓动的内部相机动画,而且先打断上一个动画再重开。
  • 指南针高频回调 → 动画无限"打断→重开"→ 表现就是"转身后等 1-2 秒才开始转 + 连续缓动"。JS 层插帧是在跟 BMapGL 自带动画打架,方向完全错了。
  • 与 3D 建筑无关:new BMapGL.Map 未开任何 3D 选项,全程 setTilt(0) 压平。

最终修法(一行参数,优雅到极致)

所有 map.setHeading(x) → map.setHeading(x, { noAnimation: true }),setTilt(x) 同理。源码走 _setValue 瞬移分支,不进动画系统。加上原有的 rAF 合帧(pendingCameraHeading 每帧只消费最新值,天然消抖),平面视图即刻切换、无连续动画、无追赶。

// 修前:每次回调触发 500ms 内部缓动,互相打断
state.map.setTilt(0);
state.map.setHeading(nextHeading);
// 修后:瞬移,只提交每帧最新的朝向
state.map.setTilt(0, { noAnimation: true });
state.map.setHeading(nextHeading, { noAnimation: true });

验证

  • 自动化:npm run test:navi 17/17 全绿(含 compass-bridge-contract)。
  • 真机:快速旋转 90°,指南针/光标立即变,地图瞬间切到新视角,无连续缓动、无追赶。与官方 App 视角跟随手感一致。

可迁移的通用判断

  1. SDK 的"旋转/相机"类方法常是自带动画的隐藏通道:遇到"数值对但视觉滞后"的先怀疑 SDK 内部动画,去扒它的实现找 noAnimation / duration / animation 开关,而不是在 JS 层叠插帧。
  2. 插帧只服务于"需要缓动的 3D 相机";平面 2D 视角跟随要的是瞬转,合帧 + 瞬移即可。
  3. 非公开 API 用之前先在官方参考文档搜一遍:setHeading/setTilt 文档里没有,行为就只能靠扒源码确认。

关联


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Frontend