交互和事件
Mapbox GL JS 交互和事件系统提供了一个强大的框架,用于处理用户与地图和 features 的交互。本文档记录了如何使用事件系统、创建自定义交互、定位特定 features 以及管理 feature 状态以进行视觉反馈。
有关可以添加到地图的 UI controls 的信息,请参阅 Controls。
事件系统概述
Mapbox GL JS 将原生浏览器事件包装到特定于地图的事件对象中,提供地理上下文和附加功能。事件系统建立在 Evented 类之上,该类提供使用 on()、once() 和 off() 注册事件监听器的方法。
事件类型和对象
有三种主要的事件对象类型:
- MapMouseEvent: 用于
click、mousemove等鼠标交互 - MapTouchEvent: 用于
touchstart、touchend等触摸交互 - MapWheelEvent: 用于滚轮事件(
wheel)
基本事件处理
要监听地图事件,使用 map.on() 方法:
// 监听地图上任何位置的点击
map.on('click', (e) => {
console.log(`在 ${e.lngLat.lng}, ${e.lngLat.lat} 点击`);
});
// 监听特定 layer 上的点击
map.on('click', 'building-layer', (e) => {
console.log('点击建筑物:', e.features[0].properties);
});阻止默认行为
事件对象包括一个 preventDefault() 方法,用于停止地图对事件的默认处理(如平移、缩放或旋转):
map.on('mousedown', (e) => {
// 阻止地图的拖动行为
e.preventDefault();
// 自定义行为...
});交互系统
Mapbox GL JS 提供一个高级交互系统,扩展了基本事件处理,允许进行更具声明式和特定于 feature 的交互。该系统围绕 InteractionSet 类构建,并通过 map.addInteraction() 方法访问。
交互流程
添加交互
使用 map.addInteraction(id, config) 方法配置交互:
map.addInteraction('building-click', {
type: 'click', // 事件类型
target: {featuresetId: 'buildings'}, // 目标描述符
filter: ['>=', ['get', 'height'], 100], // 可选过滤器
handler: (e) => { // 事件处理程序
map.setFeatureState(e.feature, {selected: true});
}
});Interaction 配置对象包括:
type: 事件类型(click、mouseenter等)target: 可选的目标描述符(layer 或 featureset)filter: 可选的表达式用于过滤 featureshandler: 接收InteractionEvent的函数
InteractionEvent
传递给处理程序的 InteractionEvent 对象包括:
- 标准事件属性(
type、point、lngLat、originalEvent) feature: 与之交互的特定 feature(作为TargetFeature)id: 交互 IDinteraction: 原始交互配置preventDefault(): 停止事件传播的方法
Feature 目标定位
Mapbox GL JS 具有灵活的系统,用于定位用于交互的特定 features 或 features 集合。
目标描述符
目标描述符可以是:
- Layer 目标:
{layerId: 'building-layer'} - Featureset 目标:
{featuresetId: 'buildings', importId?: 'basemap'}
Featuresets 是在 style 的 featuresets 属性中定义的 features 集合,可以跨越多个 layers 或 imports。
Feature 变体
当使用目标描述符查询 features 时,结果 features 包含一个 variants 属性,将目标映射到 feature 变体。当这些 features 被传递给交互处理程序时,它们被包装在表示与目标匹配的特定变体的 TargetFeature 中。
Feature 状态管理
Feature 状态允许动态样式设置和交互反馈。状态是每 feature 的键值对,可以在 style 表达式中使用。
管理 Feature 状态
// 设置 feature 状态
map.setFeatureState(feature, {selected: true, hover: false});
// 获取当前 feature 状态
const state = map.getFeatureState(feature);
// 移除特定状态属性
map.removeFeatureState(feature, 'selected');
// 移除所有状态属性
map.removeFeatureState(feature);在样式中使用 Feature 状态
Feature 状态可以使用 feature-state 操作符在 style 表达式中引用:
map.setPaintProperty('buildings', 'fill-color', [
'case',
['boolean', ['feature-state', 'selected'], false],
'#ff0000', // 选中的颜色
['boolean', ['feature-state', 'hover'], false],
'#00ff00', // 悬停颜色
'#0000ff' // 默认颜色
]);常见交互模式
悬停效果
// 添加 mouseenter 交互
map.addInteraction('building-hover', {
type: 'mouseenter',
target: {featuresetId: 'buildings'},
handler: (e) => {
map.setFeatureState(e.feature, {hover: true});
}
});
// 添加 mouseleave 交互
map.addInteraction('building-unhover', {
type: 'mouseleave',
target: {featuresetId: 'buildings'},
handler: (e) => {
map.setFeatureState(e.feature, {hover: false});
}
});选择
let selectedFeature = null;
map.addInteraction('building-select', {
type: 'click',
target: {featuresetId: 'buildings'},
handler: (e) => {
// 取消选择上一个 feature
if (selectedFeature) {
map.setFeatureState(selectedFeature, {selected: false});
}
// 选择新 feature
selectedFeature = e.feature;
map.setFeatureState(selectedFeature, {selected: true});
// 显示 popup 或详细信息
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(`<h3>${e.feature.properties.name || 'Building'}</h3>`)
.addTo(map);
}
});使用 Imports
使用导入的 styles 时,你可以定位特定 imports 中的 featuresets:
map.addInteraction('basemap-building-click', {
type: 'click',
target: {featuresetId: 'buildings', importId: 'basemap'},
handler: (e) => {
// 处理在 basemap import 中的建筑物上的点击
console.log('在 basemap 中的建筑物上点击:', e.feature);
}
});InteractionSet 实现
在底层,交互系统由 InteractionSet 实例管理,该实例处理:
- 注册和管理交互
- 将事件委托给适当的处理程序
- 为 mouseenter/mouseleave 事件跟踪悬停状态
- 使用目标描述符查询 features
- 防止交互重复
InteractionSet.handleType() 方法负责处理所有交互事件。对于 mouseenter/mouseleave 事件,handleMove() 和 handleOut() 用于跟踪状态更改并触发适当的事件。
高级主题
停止事件传播
交互处理程序可以通过返回布尔值来控制其他交互是否应该处理事件:
map.addInteraction('exclusive-click', {
type: 'click',
target: {layerId: 'important-layer'},
handler: (e) => {
// 处理点击
console.log('独占地处理点击');
// 返回 true 以停止传播到其他交互
// 返回 false(或 undefined)以允许其他交互
return true;
}
});Feature 过滤器
交互可以包含过滤器来限制哪些 features 响应交互:
map.addInteraction('tall-building-click', {
type: 'click',
target: {featuresetId: 'buildings'},
// 仅对高于 100 米的建筑物触发
filter: ['>', ['get', 'height'], 100],
handler: (e) => {
console.log('在高层建筑物上点击:', e.feature);
}
});过滤器语法使用与 Mapbox GL styles 相同的表达式系统。
Feature 命名空间
对于复杂的应用程序,你可以使用命名空间来组织 features:
map.addInteraction('city-building-click', {
type: 'click',
target: {featuresetId: 'buildings'},
namespace: 'city',
handler: (e) => {
console.log('城市建筑物命名空间:', e.feature.namespace);
// 处理在城市建筑物上的点击
}
});支持的事件类型
| 事件类型 | 描述 | 基于 |
|---|---|---|
mousedown | 鼠标按钮按下 | MouseEvent |
mouseup | 鼠标按钮释放 | MouseEvent |
click | 鼠标点击 | MouseEvent |
dblclick | 双击 | MouseEvent |
mousemove | 鼠标移动 | MouseEvent |
mouseenter | 鼠标进入 feature | MouseEvent(委托) |
mouseleave | 鼠标离开 feature | MouseEvent(委托) |
mouseover | mouseenter 的别名 | MouseEvent(委托) |
mouseout | mouseleave 的别名 | MouseEvent(委托) |
touchstart | 触摸开始 | TouchEvent |
touchend | 触摸结束 | TouchEvent |
touchcancel | 触摸被取消 | TouchEvent |
wheel | 鼠标滚轮移动 | WheelEvent |
实现细节
交互和事件系统通过几个关键组件实现:
- 地图事件(在
src/ui/events.ts中)使用特定于地图的上下文包装 DOM 事件 - InteractionSet(在
src/ui/interactions.ts中)管理交互和委托的事件 - Feature 目标定位(在
src/util/vectortile_to_geojson.ts中)处理 feature 变体和目标 features - 查询 features(在
src/source/query_features.ts中)使用目标描述符执行 feature 查找 - Feature 状态 存储在 style 系统中并用于渲染
当使用 map.addInteraction() 添加交互时,系统:
- 在 InteractionSet 中创建一个条目
- 如有需要则设置事件监听器
- 对于与悬停相关的事件,设置 mousemove/mouseout 委托
- 当事件发生时,系统:
- 创建 MapMouseEvent/MapTouchEvent
- 委托给 InteractionSet.handleType()
- 基于目标描述符查询 features
- 应用过滤器
- 为匹配的 features 创建 TargetFeature 实例
- 使用 InteractionEvent 对象调用处理程序