> **本文回答**：视图变更事件 —— move / moveend / zoom / rotate / pitch 以及何时用 start/end。

## 一、事件对

| 起 / 进行 / 止 |
|---|
| `movestart` / `move` / `moveend` |
| `zoomstart` / `zoom` / `zoomend` |
| `rotatestart` / `rotate` / `rotateend` |
| `pitchstart` / `pitch` / `pitchend` |
| `dragstart` / `drag` / `dragend` |

## 二、move 组合

```js
map.on('move', () => console.log(map.getCenter()));   // 每帧 fire
map.on('moveend', () => save(map.getCenter()));        // 停稳后 fire 一次
```

- `move` 高频，用于实时联动（比如指北针跟随旋转）
- `moveend` 低频，适合持久化 / 请求

## 三、zoom

```js
map.on('zoom', () => hud.zoom = map.getZoom().toFixed(1));
map.on('zoomend', () => fetchTilesForZoom(map.getZoom()));
```

## 四、rotate / pitch

```js
map.on('rotate', () => {
  compass.style.transform = `rotate(${-map.getBearing()}deg)`;
});
map.on('pitchend', () => {
  if (map.getPitch() === 0) reset2D();
});
```

## 五、drag（平移拖拽）

```js
map.on('dragstart', () => isDragging = true);
map.on('dragend',   () => isDragging = false);
```

## 六、高频事件 + RAF

```js
let pending = false;
map.on('move', () => {
  if (pending) return;
  pending = true;
  requestAnimationFrame(() => {
    update();
    pending = false;
  });
});
```

或直接用 `moveend`：

```js
map.on('moveend', () => update());
```

## 七、地图边界监听

```js
map.on('moveend', () => {
  const bounds = map.getBounds();
  loadDataInBounds(bounds);
});

// CAD 坐标（当前视野）
map.on('moveend', () => {
  // ⚠️ 不要用 map.getGeoBounds()——它返回"全图范围×ratio"（不随视野变化）。
  // 当前视野的 CAD 范围：把 LngLatBounds 反算
  const cadBounds = map.fromLngLat(map.getBounds());   // 传 LngLatBounds 直接返回 GeoBounds
  // 旋转 / 倾斜时可逐角点：map.fromLngLat(map.unproject([0,0])) / unproject([w,h])
});
```

## 八、程序化移动也 fire 事件

`flyTo / easeTo / jumpTo / fitBounds` 都会触发 move 事件。避免循环：

```js
let suppress = false;
map.on('move', () => {
  if (suppress) return;
  // 处理用户拖动
});

suppress = true;
map.flyTo({ center: [116, 39] });
map.once('moveend', () => suppress = false);
```

## 九、视图参数速查

| 方法 | 返回 |
|---|---|
| `getCenter()` | `{ lng, lat }` |
| `getZoom()` | number |
| `getBearing()` | 度（0 = 正北） |
| `getPitch()` | 度（0 = 水平） |
| `getBounds()` | `LngLatBounds` |
| `getGeoBounds(ratio?)` | 全图范围 × ratio 的 CAD GeoBounds，不随视野变化；当前视野用 `map.fromLngLat(map.getBounds())` |

## 十、同步多图视图

见 [overlay/sync-view](../overlay/sync-view.md)。核心：

```js
map1.on('move', () => {
  if (syncing) return;
  syncing = true;
  map2.jumpTo({
    center: map1.getCenter(),
    zoom: map1.getZoom(),
    bearing: map1.getBearing(),
    pitch: map1.getPitch()
  });
  syncing = false;
});
```

## 十一、常见坑

1. **在 `move` 里 `flyTo`**：无限递归。
2. **`moveend` 后立即 setData**：仍然是同一帧，可能卡；考虑放到 `idle` 后。
3. **bearing = 0 不等于无旋转**：接近 0 可能 0.0001；用 `Math.abs(map.getBearing()) < 0.1` 判断"近乎正北"。
4. **pitch > 0 下 mousemove 坐标偏差**：远处的像素映射到 CAD 坐标范围很大；用业务的距离阈值。
5. **moveend 不 fire**：如果编辑 canvas 位置 / 尺寸没调用 `map.resize()`，内部状态未变 → 可能不触发；手动 `map.resize()`。

## 相关

- 交互 → [map-interaction](map-interaction.md)
- 性能 → [perf/event-throttling](../perf/event-throttling.md)
- 视图同步 → [overlay/sync-view](../overlay/sync-view.md)
