ai-coding
2026-06-30 75b0d49ae9e8e4daedb6d68e46e67e47790b3bde
fix(map): 用户手势期间不触发 rerender

Co-Authored-By: 卢春龙 <lucl@xiaopeng.com>
2个文件已修改
27 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/hooks/use-smooth-route-motion.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-smooth-route-motion.ts
@@ -36,6 +36,8 @@
  setVehicleMoveAlongPoints: (points: RoutePoint[]) => void;
  /** 节流更新车辆位置 */
  updateVehicleLocationThrottled: (lat: number, lng: number, force?: boolean) => void;
  /** 读取用户手势是否进行中(拖拽/缩放手势 begin→end 之间为 true)。 */
  getUserGestureActive?: () => boolean;
};
type MotionRuntimeState = {
@@ -292,6 +294,14 @@
      if (!runtime.routeReady || !routeIndex) {
        return;
      }
      // 用户手势(拖拽/缩放)进行中暂停补帧:本帧会通过 updateVehicleLocationThrottled setState 触发
      // 页面重渲染,进而把受控 <Map> 的相机重新写回原生地图,与用户手势争抢导致「拖动被弹回」。
      // 整帧跳过(车标/擦线进度一并冻结,保持一致),并刷新 lastRenderAt 避免手势结束后步长突跳;
      // 手势期间 socket 仍可推进 targetDistanceMeters,手势结束后由后续帧平滑追帧。
      if (deps.getUserGestureActive?.()) {
        lastRenderAtRef.current = Date.now();
        return;
      }
      const now = Date.now();
      const lastRenderAt = lastRenderAtRef.current || now;
      const deltaMs = Math.max(TripMotionConfig.RENDER_INTERVAL_MS, now - lastRenderAt);
src/robotaxi/pages/begin-route/index.tsx
@@ -241,6 +241,9 @@
  const markViewportFitSyncing = useCallback(() => {
    globalFitSyncRef.current = true;
  }, []);
  // 用户手势进行中标记:从 regionchange 'begin'(用户拖拽/缩放手势) 到 'end' 之间为 true。
  // 供平滑运动在手势期间暂停车辆位置回灌,避免高频 setState 触发重渲染把受控相机弹回。
  const userGestureActiveRef = useRef(false);
  // 经纬度 setter:更新 state 触发渲染的同时同步记录最新值,保证 ref 始终为最新(兼容函数式更新)
  const setLatitude = useCallback((value: number | null | ((prev: number | null) => number | null)) => {
    setLatitudeState((prev) => {
@@ -1301,7 +1304,8 @@
    setRouteDuration,
    setVehicleDirection,
    setVehicleMoveAlongPoints,
    updateVehicleLocationThrottled: vehicleState.updateVehicleLocationThrottled
    updateVehicleLocationThrottled: vehicleState.updateVehicleLocationThrottled,
    getUserGestureActive: () => userGestureActiveRef.current
  });
  useEffect(() => {
@@ -1722,6 +1726,17 @@
    // 避免受控 <Map scale={mapScale}> 与原生实际缩放漂移,影响后续视口编排判断。
    const causedBy = String(e?.causedBy || e?.detail?.causedBy || '').toLowerCase();
    const eventType = String(e?.type || e?.detail?.type || '').toLowerCase();
    // 维护用户手势进行中标记:手势 begin 置位、end 复位,供平滑运动在手势期间暂停车辆位置回灌。
    const isUserGesture = causedBy === 'scale' || causedBy === 'drag' || causedBy === 'gesture';
    if (eventType === 'begin') {
      if (isUserGesture) {
        userGestureActiveRef.current = true;
      }
    } else if (eventType === 'end' && isUserGesture) {
      // 仅用户手势(gesture/drag/scale)的 end 才复位;手势期间穿插的程序化 update 事件
      // (causedBy='update') 的 end 不应清除标记,否则会在用户仍在操作时提前恢复补帧导致弹回。
      userGestureActiveRef.current = false;
    }
    if (eventType === 'end' && (causedBy === 'scale' || causedBy === 'drag' || causedBy === 'gesture')) {
        notifyUserMapGesture();