> **本文回答**：地图交互控制开关 —— 拖动 / 旋转 / 缩放 / 双击 / 触摸 / pitch。

## 一、控制对象（vjmap 手势）

| 交互 | map 属性 |
|---|---|
| 拖动平移 | `map.dragPan` |
| 按住鼠标旋转 | `map.dragRotate` |
| 滚轮缩放 | `map.scrollZoom` |
| 键盘 | `map.keyboard` |
| 双击缩放 | `map.doubleClickZoom` |
| 触摸缩放 | `map.touchZoomRotate` |
| 触摸 pitch | `map.touchPitch` |
| 边界 box zoom | `map.boxZoom` |

每个对象都有 `enable()`, `disable()`, `isEnabled()`。

## 二、创建时禁用

```js
new vjmap.Map({
  container: "map",
  style: ...,
  // 在构造器里关掉
  dragPan: false,
  dragRotate: false,
  scrollZoom: false,
  keyboard: false,
  doubleClickZoom: false,
  touchZoomRotate: false,
  boxZoom: false
});
```

## 三、动态开关

```js
map.scrollZoom.disable();
map.scrollZoom.enable();
console.log(map.scrollZoom.isEnabled());

// 只允许缩放，不允许旋转
map.dragRotate.disable();
map.touchZoomRotate.disableRotation();
```

## 四、限制最大 / 最小 zoom

```js
map.setMinZoom(0);
map.setMaxZoom(22);

// 构造时
new vjmap.Map({ minZoom: 12, maxZoom: 18, ... });
```

## 五、限制 pitch 范围

```js
map.setMinPitch(0);
map.setMaxPitch(85);   // 默认最大 85（引擎上限也是 85）
// 禁止 pitch:
map.setMaxPitch(0);
```

## 六、限制 bearing

无直接 setMaxBearing。可以 `map.on("rotate", () => { if (map.getBearing() > 45) map.setBearing(45); })`。

## 七、限制平移范围

```js
map.setMaxBounds(map.toLngLat(prj.getMapExtent()));
// 或
map.setMaxBounds([[lngW, latS], [lngE, latN]]);

// 取消限制
map.setMaxBounds(null);
```

## 八、触摸交互

```js
map.touchZoomRotate.enable();
map.touchZoomRotate.disableRotation();   // 禁双指旋转但保留缩放
map.touchPitch.enable();
```

## 九、程序化动画

```js
map.jumpTo({ center: [0,0], zoom: 10 });             // 瞬间
map.panTo([lng, lat], { duration: 1000 });
map.zoomTo(12, { duration: 500 });
map.rotateTo(60, { duration: 500 });
map.fitBounds([[-1, -1], [1, 1]], { padding: 50, duration: 800 });
map.flyTo({
  center: [lng, lat],
  zoom: 14, pitch: 60, bearing: -20,
  speed: 1.2, curve: 1.4,
  essential: true
});
map.fitMapBounds();   // vjmap 扩展，全图
```

`essential: true` 把动画标记为"必要"——**不受** `prefers-reduced-motion` 影响（用户开了减少动态效果也照常播放）；缺省/`false` 时系统设置会减少动画。

## 十、判断是否交互中

```js
if (map.isMoving() || map.isZooming() || map.isRotating()) {
  // 正在动
}

// vjmap 扩展：
map.setIsInteracting(true);    // 手动标记
map.setIsInteracting(false);
```

## 十一、性能 trick

- 动画期间关闭贵重渲染：

  ```js
  map.on("movestart", () => map.setLayoutProperty("my-heavy-layer", "visibility", "none"));
  map.on("moveend",   () => map.setLayoutProperty("my-heavy-layer", "visibility", "visible"));
  ```

- 用 `map.easeTo({ center, duration: 0 })` 代替 `jumpTo` 避免某些 GL bug。

## 十二、常见坑

1. **用户滚轮误缩放** → `map.scrollZoom.disable()` 并提供按钮缩放。
2. **maxBounds 太紧 → pan 抖动** → 适当放宽 bound 边界（例如 +10% padding）。
3. **`flyTo` 过远 → 花很长时间**：调 `speed` 和 `curve`，或改用 `jumpTo`。
4. **双击缩放与自定义双击冲突** → `doubleClickZoom: false`，自己监听 `dblclick`。

5. **Handler 类本身不导出**：`map.boxZoom/scrollZoom/dragPan/dragRotate/keyboard/doubleClickZoom/touchZoomRotate/touchPitch` 八个**实例**的 `isEnabled()/enable()/disable()/isActive()` 全部可用（enable/disable 往返true→false→true），但 `vjmap.BoxZoomHandler`/`ScrollZoomHandler`/`DragPanHandler`/`DragRotateHandler`/`KeyboardHandler`/`DoubleClickZoomHandler`/`TouchZoomRotateHandler`/`TouchPitchHandler` 八个**类**在运行时全是 `undefined`（d.ts 声明为 `export class`，产物里未导出）——只能经 `map.*` 实例操作，不能 `new` 也不能 `instanceof`。
6. **MapEvent 族四件同样只声明不导出**：`vjmap.MapEvent/MapMouseEvent/MapTouchEvent/MapWheelEvent` 运行时全 `undefined`（min.js 里连标识符都被 shake 掉）。事件回调拿到的是内部构造实例（click 载荷 ctor=`Bn`，键=`point,lngLat,originalEvent,type,_defaultPrevented,target`，`preventDefault()/defaultPrevented` 可用）——字段名与文档一致，只是类名不可达。

7. **zoomIn/zoomOut/resetNorth/snapToNorth 是 easeTo 族动画方法**：调用后立即 `getZoom()/getBearing()` 读到的是**动画前值**（zoomIn 后 zoom 数字不变是正常——动画帧推进中）；`setBearing` / `setPadding` 则走 `jumpTo`，**立即生效、无动画**（调用后马上 `getBearing()` 即新值）。这些方法都同步派发 `move` 系列事件——若页面上残留失效的 store/draw 监听器，异常会**同步穿透回 camera 调用栈**（`setBearing`/`setPadding` 抛 `Cannot read properties of null (reading 'getAll')` 即源于此类监听污染，非方法本身缺陷）。断言相机状态要等 `moveend`/`idle` 事件或延时后再读。`setMaxBounds`/`getMaxBounds`/`cameraForBounds`/`fitScreenCoordinates`/`getFreeCameraOptions`/`setRenderWorldCopies` 是同步往返，不受此影响。
