mouse-keyboard
本文回答:地图鼠标和键盘事件 —— 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 |
二、带图层过滤的事件
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 坐标获取
map.on("click", (e) => {
const lngLat = e.lngLat;
const cad = map.fromLngLat(lngLat); // {x, y} CAD 原始
});
五、鼠标穿透 / 禁用
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 全局(更可靠)
window.addEventListener("keydown", (e) => {
if (e.key === "Escape") cancelCurrentAction();
});
七、双击 / 右键禁用
map.doubleClickZoom.disable();
map.on("contextmenu", () => {}); // 只要有 contextmenu 监听,引擎就屏蔽浏览器右键菜单;回调里 e.preventDefault() 不起这个作用
初始化时:
new vjmap.Map({ doubleClickZoom: false, ... });
八、查询命中要素
const features = map.queryRenderedFeatures(e.point, {
layers: ["layer1", "layer2"],
filter: ["==", ["get", "type"], "road"]
});
// 框选范围内
const features2 = map.queryRenderedFeatures([[x1, y1], [x2, y2]], { layers: [...] });
九、指针样式
map.getCanvas().style.cursor = "pointer";
map.getCanvas().style.cursor = "crosshair";
map.getCanvas().style.cursor = ""; // 还原
十、拖拽 / 点选冲突
按下到抬起位移 ≥ clickTolerance(默认 3px)时引擎本就不派发 click,拖图不会误触发 click。若仍需拖拽标志,必须在 mousedown 复位(否则拖拽后没有 click 来清标志,会吞掉下一次真正的点击):
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 回调要节流:
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));
十二、常见坑
- click 未 fire:canvas 上层有 div 遮挡。
- e.features 空:图层 id 错了 / 图层在 map.addLayer 之前。
- contextmenu 不触发:多半被上层 DOM 遮挡(同 click);用
map.on("contextmenu", ...)而不是 window 的。 map.on("keydown")不 fire:地图不派发键盘事件,改用 DOMaddEventListener(见 §六);DOM 监听挂在地图容器上时还需 canvas 聚焦。