> **本文回答**：`map.on` 鼠标 / 触摸 / 键盘事件 —— click / mousemove / wheel / contextmenu / touch。

## 一、鼠标事件列表

| 事件 | 说明 |
|---|---|
| `click` | 单击 |
| `dblclick` | 双击 |
| `contextmenu` | 右键 |
| `mousedown` / `mouseup` | 按下 / 抬起 |
| `mousemove` | 移动（高频） |
| `mouseover` / `mouseout` | 进入 / 离开 canvas |
| `mouseenter` / `mouseleave` | 仅 `map.on(type, layerId, fn)` 有效（进入 / 离开该图层要素）；不带 layerId 永不触发 |
| `wheel` | 滚轮 |
| `dragstart` / `drag` / `dragend` | 拖拽 |

## 二、事件对象结构

```ts
interface MapMouseEvent {
  type: string;
  lngLat: { lng, lat };
  point: { x, y };            // 容器像素
  originalEvent: MouseEvent;
  features?: Feature[];       // 带图层过滤时才有
  preventDefault(): void;
}
```

CAD 坐标：

```js
map.on('click', e => {
  const lngLat = e.lngLat;
  const cad = map.fromLngLat(lngLat);    // {x, y}
});
```

## 三、带图层过滤

```js
map.on('click', 'my-layer', e => {
  console.log(e.features[0].properties);
});
map.on('mouseenter', 'my-layer', () => map.getCanvas().style.cursor = 'pointer');
map.on('mouseleave', 'my-layer', () => map.getCanvas().style.cursor = '');
```

图层级事件只在命中该图层要素时触发。

## 四、触摸事件

| 事件 | 说明 |
|---|---|
| `touchstart` / `touchmove` / `touchend` / `touchcancel` | 触摸 |

```js
map.on('touchstart', e => console.log(e.points));   // 多指时 points 是数组
```

## 五、键盘事件

vjmap 不派发地图级键盘事件：`map.on('keydown' / 'keyup')` 收不到真实按键（canvas 上的按键只交给内置键盘平移 / 缩放 handler；仅 `map.fire('keyup', …)` 程序化派发能触发）。用 DOM 监听：

```js
map.getCanvas().focus();   // canvas 获得焦点后按键才会落到地图容器上
map.getCanvasContainer().addEventListener('keydown', e => console.log(e.key));
// 或全局：window.addEventListener('keydown', e => ...)
```

## 六、右键菜单

只要有 `map.on('contextmenu')` 监听（或 dragRotate 处于启用状态），引擎自动对原生事件调 `preventDefault`，浏览器菜单不会弹出：

```js
map.on('contextmenu', e => {
  // 无需 e.preventDefault()：它只给内部 handler 打标记，不是用来防浏览器菜单的
  showMyMenu(e.point.x, e.point.y);
});
```

右键拖拽默认是旋转 / 俯仰（不是平移），禁用：

```js
map.dragRotate.disable();
```

## 七、滚轮 / 缩放

```js
map.on('wheel', e => {
  // e.originalEvent.deltaY
});

map.on('zoom', () => console.log(map.getZoom()));
```

禁用滚轮缩放：

```js
map.scrollZoom.disable();
```

## 八、拖拽识别

按下到抬起位移 ≥ `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
});
```

## 九、指针样式

```js
map.getCanvas().style.cursor = 'pointer';
map.getCanvas().style.cursor = 'crosshair';
map.getCanvas().style.cursor = '';
```

## 十、节流高频事件

`mousemove` 每秒 60+ 次：

```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));
```

或用 `requestAnimationFrame` 合并：

```js
let pending = false;
map.on('mousemove', e => {
  if (pending) return;
  pending = true;
  requestAnimationFrame(() => {
    // 处理
    pending = false;
  });
});
```

## 十一、常见坑

1. **click 不 fire**：canvas 上层有 DOM 挡住。
2. **e.features 空**：图层 id 错 / 图层未 add。
3. **contextmenu 不 fire**：多半被上层 DOM 遮挡（同 click）。
4. **`map.on('keydown')` 不 fire**：地图不派发键盘事件，改用 DOM `addEventListener`（见 §五）。
5. **高频 mousemove 卡顿**：必节流。
6. **addControl 前绑定事件** → 有时候事件 ordering 导致控件先接管；尽量 await `map.onLoad()` 后再绑。

## 相关

- 生命周期 → [map-lifecycle](map-lifecycle.md)
- 图层级事件 → [layer-events](layer-events.md)
- 节流 / RAF → [perf/event-throttling](../perf/event-throttling.md)
