> **本文回答**：地图鼠标和键盘事件 —— click / mousemove / wheel / keydown。

## 一、vjmap 事件体系

`map.on(type, handler)` 可监听的事件如下（覆盖视图 / 鼠标 / 触摸 / 生命周期）：

| 类别 | 事件 |
|---|---|
| 视图 | `move`, `movestart`, `moveend`, `zoom`, `zoomstart`, `zoomend`, `rotate`, `rotatestart`, `rotateend`, `pitch`, `pitchstart`, `pitchend` |
| 鼠标（地图级） | `click`, `dblclick`, `contextmenu`, `mousedown`, `mouseup`, `mousemove`, `mouseover`, `mouseout`, `wheel` |
| 鼠标（仅图层委托） | `mouseenter`, `mouseleave`（`mouseover`/`mouseout` 也有图层形态）——必须 `map.on(type, layerId, fn)`，不带图层不会触发 |
| 触摸 | `touchstart`, `touchend`, `touchmove`, `touchcancel` |
| 拖拽 | `dragstart`, `drag`, `dragend` |
| 框选 | `boxzoomstart`, `boxzoomend`, `boxzoomcancel` |
| 加载 | `load`, `idle`, `render`, `sourcedata`, `styledata`, `error` |
| 数据 | `data`, `dataloading`, `sourcedata`, `sourcedataloading`, `styledataloading`, `tiledataloading` |

## 二、带图层过滤的事件

```js
map.on("click", "my-layer", (e) => {
  const feature = e.features[0];
  console.log(feature.properties);
});
map.on("mouseenter", "my-layer", () => map.getCanvas().style.cursor = "pointer");
map.on("mouseleave", "my-layer", () => map.getCanvas().style.cursor = "");
```

## 三、事件对象

| 字段 | 说明 |
|---|---|
| `e.lngLat` | `{ lng, lat }` 经纬度 |
| `e.point` | `{ x, y }` 容器像素 |
| `e.originalEvent` | 原生 `MouseEvent` / `WheelEvent` |
| `e.features` | 命中的要素数组（带图层名过滤时才有） |
| `e.type` | 事件名 |

## 四、CAD 坐标获取

```js
map.on("click", (e) => {
  const lngLat = e.lngLat;
  const cad = map.fromLngLat(lngLat);   // {x, y} CAD 原始
});
```

## 五、鼠标穿透 / 禁用

```js
map.on("click", (e) => {
  // 点击命中某图层就处理，否则返回
  const features = map.queryRenderedFeatures(e.point, { layers: ["my-layer"] });
  if (features.length === 0) return;
  // ...
});
```

## 六、键盘事件

- vjmap **不派发地图级键盘事件**：`map.on("keydown" / "keyup", ...)` 收不到真实按键（canvas 上的按键只交给内置键盘平移 / 缩放 handler）。
- 监听真实按键用 DOM：`map.getCanvas().focus(); map.getCanvasContainer().addEventListener("keydown", e => ...)`，或挂 `window`（见下）。
- 模拟按键：`map.fire("keyup", { keyCode: 13 })`（程序化派发，`map.on("keyup")` 与 Draw 能收到；上面测量章节用过）。

### 绑定 window 全局（更可靠）

```js
window.addEventListener("keydown", (e) => {
  if (e.key === "Escape") cancelCurrentAction();
});
```

## 七、双击 / 右键禁用

```js
map.doubleClickZoom.disable();
map.on("contextmenu", () => {});   // 只要有 contextmenu 监听，引擎就屏蔽浏览器右键菜单；回调里 e.preventDefault() 不起这个作用
```

初始化时：

```js
new vjmap.Map({ doubleClickZoom: false, ... });
```

## 八、查询命中要素

```js
const features = map.queryRenderedFeatures(e.point, {
  layers: ["layer1", "layer2"],
  filter: ["==", ["get", "type"], "road"]
});

// 框选范围内
const features2 = map.queryRenderedFeatures([[x1, y1], [x2, y2]], { layers: [...] });
```

## 九、指针样式

```js
map.getCanvas().style.cursor = "pointer";
map.getCanvas().style.cursor = "crosshair";
map.getCanvas().style.cursor = "";   // 还原
```

## 十、拖拽 / 点选冲突

按下到抬起位移 ≥ `clickTolerance`（默认 3px）时引擎本就不派发 click，拖图不会误触发 click。若仍需拖拽标志，必须在 mousedown 复位（否则拖拽后没有 click 来清标志，会吞掉下一次真正的点击）：

```js
let didDrag = false;
map.on("mousedown", () => didDrag = false);
map.on("dragstart", () => didDrag = true);
map.on("click", (e) => {
  if (didDrag) return;
  // 真正的 click
});
```

或用 `map.on("mouseup", ...)` + 判断起点与终点位移。

## 十一、Hover 防抖

高频 `mousemove` 回调要节流：

```js
function throttle(fn, ms) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last > ms) { last = now; fn(...args); }
  };
}
map.on("mousemove", throttle(e => {
  // 昂贵查询
}, 100));
```

## 十二、常见坑

1. **click 未 fire**：canvas 上层有 div 遮挡。
2. **e.features 空**：图层 id 错了 / 图层在 map.addLayer 之前。
3. **contextmenu 不触发**：多半被上层 DOM 遮挡（同 click）；用 `map.on("contextmenu", ...)` 而不是 window 的。
4. **`map.on("keydown")` 不 fire**：地图不派发键盘事件，改用 DOM `addEventListener`（见 §六）；DOM 监听挂在地图容器上时还需 canvas 聚焦。
