layer-events
本文回答:图层级事件 ——
map.on('click', layerId, handler)只在命中特定图层要素时触发。
一、图层级 vs 全局级
map.on('click', handler); // 全局:任意位置
map.on('click', 'my-layer', handler); // 图层:只命中 my-layer 里的要素
图层级传第二参,要素信息在 e.features:
map.on('click', 'roads', e => {
const feat = e.features[0];
console.log(feat.properties.road_name);
});
二、支持的事件
| 事件 | 说明 |
|---|---|
click |
命中要素时 |
dblclick |
双击要素 |
mouseenter |
进入要素 |
mouseleave |
离开要素 |
mousemove |
在要素上移动 |
mousedown / mouseup |
|
contextmenu |
右键 |
touchstart / touchend |
移动端 |
三、常见用途
1 指针样式
map.on('mouseenter', 'buildings', () => map.getCanvas().style.cursor = 'pointer');
map.on('mouseleave', 'buildings', () => map.getCanvas().style.cursor = '');
2 点击显示 Popup
map.on('click', 'pois', e => {
const f = e.features[0];
new vjmap.Popup()
.setLngLat(f.geometry.coordinates)
.setHTML(`<b>${f.properties.name}</b>`)
.addTo(map);
});
3 Hover 高亮(feature-state)
let hovered = null;
map.on('mousemove', 'roads', e => {
if (hovered !== null) map.setFeatureState({ source: 'roads-src', id: hovered }, { hover: false });
hovered = e.features[0].id;
map.setFeatureState({ source: 'roads-src', id: hovered }, { hover: true });
});
map.on('mouseleave', 'roads', () => {
// 用 !== null 而非 if (hovered):generateId 从 0 编号,0 为假值,第一个要素的 hover 会清不掉
if (hovered !== null) map.setFeatureState({ source: 'roads-src', id: hovered }, { hover: false });
hovered = null;
});
// 样式里
map.addLineLayer('roads', 'roads-src', {
lineColor: ['case', ['boolean', ['feature-state', 'hover'], false], '#f00', '#888'],
lineWidth: 3
});
四、e.features 数据
map.on('click', 'my-layer', e => {
for (const f of e.features) {
f.id // 数字 / 字符串(需 promoteId 或 generateId)
f.properties // 对象
f.geometry // GeoJSON 几何
f.layer // 图层样式定义
f.source // 来源 id
f.sourceLayer // 矢量瓦片子图层
}
});
五、多图层监听
['layer1', 'layer2', 'layer3'].forEach(id => {
map.on('click', id, e => handle(e, id));
});
或用 queryRenderedFeatures 一次过滤:
map.on('click', e => {
const feats = map.queryRenderedFeatures(e.point, {
layers: ['layer1', 'layer2', 'layer3']
});
if (feats.length === 0) return;
handle(feats[0]);
});
或直接用 map 上内置的 map-gl-utils 助手(layers 可传 id / 数组 / 正则,clickOneLayer 只收数组;都返回解绑函数):
const offPointer = map.hoverPointer(['layer1', 'layer2']); // 悬停变手型
const offHover = map.hoverFeatureState('roads'); // 自动维护 feature-state hover(id 为 0 的要素不生效)
const offClick = map.clickOneLayer(['layer1', 'layer2', 'layer3'],
({ layer, feature }) => handle(feature, layer), // 只派发给第一个命中的图层
e => console.log('点到空白处', e.lngLat)); // 全部未命中
// 另有 map.clickLayer / hoverLayer / hoverPopup / clickPopup
offClick(); // 解绑
六、事件触发顺序
同时绑定全局级和图层级时,触发顺序 = 注册顺序(图层级本质是包了一层 queryRenderedFeatures 过滤的普通监听器,源码 _createDelegatedListener 证实):
map.on('click', e => ...); // 先注册先跑——即使图层级后注册也排后面
map.on('click', 'my-layer', e => ...);
⚠️ e.preventDefault() 不会阻止后续监听器执行(Evented.fire 不检查 defaultPrevented,只标记事件)——要阻止全局 handler,在全局 handler 里自查命中:
map.on('click', e => {
if (map.queryRenderedFeatures(e.point, { layers: ['my-layer'] }).length) return;
// 真正的"空白处"逻辑
});
七、结合 popup 关闭
let popup = null;
map.on('click', 'pois', e => {
if (popup) popup.remove();
popup = new vjmap.Popup().setLngLat(e.lngLat).setHTML(html(e.features[0])).addTo(map);
});
map.on('click', e => {
// 全局 click:检查是否命中图层,没命中就关 popup
const hit = map.queryRenderedFeatures(e.point, { layers: ['pois'] });
if (hit.length === 0 && popup) { popup.remove(); popup = null; }
});
八、off 取消
const handler = e => { ... };
map.on('click', 'my-layer', handler);
// 取消
map.off('click', 'my-layer', handler);
必须传相同的 layerId 和 handler 引用。
九、常见坑
- 可先绑后加图层:委托监听每次事件时才
getLayer(layerId),图层不存在只是不触发;先绑后 addLayer、或 removeLayer 后重加都照常生效。 - 匿名 handler 无法 off:保留引用。
- e.features 空:可能 source 刚 setData 还没完;在
sourcedata后。 - 重叠要素:handler 每次点击只调一次,
e.features含该图层在该点下的全部要素(自上而下),只要最上层取[0];不同图层各自的图层级监听都会触发。 - 点击空白也 fire 全局 click:
e.featuresundefined,不会 fire 图层级;用queryRenderedFeatures判命中。 e.features只在 handler 同步执行期间有效:_createDelegatedListener在 handler 返回后立即delete e.features——把事件对象存起来异步读、或resolve(e)后再访问,features都是undefined。要保留数据就在 handler 内同步取值/拷贝:const tag = e.features[0].properties.tag。- 刚 addSource+addLayer 立刻
queryRenderedFeatures会返回空:GeoJSON source 的瓦片由 worker 异步生成,需轮询或等sourcedata事件后再查(通常约 0.4–2s 才命中)。
相关
- 交互事件 → map-interaction
- feature-state → batch/geojson-bulk
- Popup → popup/overview