弹窗与提示框
本文档涵盖 Leaflet 中的弹窗和提示框覆盖层系统。弹窗和提示框是基于 HTML 的覆盖层,在特定地理位置的地图图层上方显示信息。两者都扩展自共同的 DivOverlay 基类,可以绑定到图层或独立显示。
关于其他覆盖层类型(如图片覆盖层)的信息,请参阅 瓦片图层与网格系统。关于也可以绑定弹窗/提示框的图层组和要素组的信息,请参阅 图层组与要素组。
架构概览
覆盖层系统建立在三层类层次结构上,其中 DivOverlay 为 Popup 和 Tooltip 提供基础。
类层次结构
DivOverlay 基类
DivOverlay 类为所有位于地理坐标位置的基于 HTML 的覆盖层提供共享功能。
| 特性 | 描述 | 实现 |
|---|---|---|
| 内容管理 | 支持字符串、HTMLElement 或函数内容 | src/layer/DivOverlay.js158-171 |
| 位置绑定 | 将覆盖层链接到 LatLng 坐标 | src/layer/DivOverlay.js141-156 |
| 源跟踪 | 引用打开覆盖层的图层 | src/layer/DivOverlay.js45-56 |
| 交互模式 | 覆盖层容器上的可选事件监听 | src/layer/DivOverlay.js120-123 |
| Pane 管理 | 通过地图 pane 系统控制 z-index | src/layer/DivOverlay.js34-36 |
| FeatureGroup 支持 | 自动从组中选择可见图层 | src/layer/DivOverlay.js232-269 |
关键方法:
openOn(map)- 将覆盖层添加到地图 src/layer/DivOverlay.js58-67close()- 从地图移除覆盖层 src/layer/DivOverlay.js69-76toggle(layer)- 根据当前状态打开或关闭 src/layer/DivOverlay.js78-97setLatLng(latlng)- 更新位置并触发更新 src/layer/DivOverlay.js149-156setContent(content)- 更新内容并触发更新 src/layer/DivOverlay.js167-171update()- 刷新内容、布局和位置 src/layer/DivOverlay.js181-193
Popup 类
Popup 类实现模态风格的覆盖层,具有自动关闭行为和视口管理。
配置选项
DOM 结构
弹窗创建分层的 HTML 结构,包含内容包装器、提示(箭头)和可选的关闭按钮。
自动平移机制
当 autoPan: true 时,地图自动平移以确保弹窗在视口中完全可见。
计算逻辑:
- 将弹窗位置转换为容器点 src/layer/Popup.js298
- 计算每个方向的溢出(右、左、下、上)src/layer/Popup.js306-317
- 应用 padding 偏移 src/layer/Popup.js299-302
- 如果非零则按计算的 delta 平移 src/layer/Popup.js323-332
自动关闭行为
弹窗实现类似单例的行为,打开新弹窗会自动关闭前一个。
| 场景 | 行为 | 实现 |
|---|---|---|
使用 autoClose: true 打开新弹窗 | 关闭 map._popup(如果存在) | src/layer/Popup.js138-147 |
使用 autoClose: false 打开弹窗 | 多个弹窗可以共存 | src/layer/Popup.js141 |
使用 closePopupOnClick: true 点击地图 | 通过 preclick 事件关闭弹窗 | src/layer/Popup.js193-205 |
使用 closeOnEscapeKey: true 按下 ESC 键 | 关闭弹窗(由地图处理) | src/layer/Popup.js114-117 |
使用 ResizeObserver 的内容跟踪
弹窗使用 ResizeObserver 检测内容大小变化(例如图片加载)并自动重新定位。
Tooltip 类
Tooltip 类实现轻量级、非模态覆盖层,具有方向定位和指针跟随功能。
配置选项
| 选项 | 默认值 | 描述 |
|---|---|---|
direction | 'auto' | 放置位置:'right', 'left', 'top', 'bottom', 'center', 'auto' |
permanent | false | 永久显示而不是悬停时显示 |
sticky | false | 跟随指针而不是固定位置 |
opacity | 0.9 | 容器不透明度 |
offset | [0, 0] | 像素位置偏移 |
pane | 'tooltipPane' | 用于渲染的地图 pane |
方向定位
提示框根据方向计算位置,当方向为 'auto' 时自动切换到 'left' 或 'right'。
定位算法:
- 确定方向(自动或显式)src/layer/Tooltip.js165-188
- 从提示框尺寸计算偏移 src/layer/Tooltip.js165-188
- 应用 CSS 类进行方向样式设置 src/layer/Tooltip.js192-199
- 设置最终 DOM 位置 src/layer/Tooltip.js199
Sticky 模式
当 sticky: true 时,提示框跟随指针而不是锚定到固定位置。
永久与临时提示框
提示框可以是永久的(始终可见)或临时的(悬停/聚焦时显示)。
| 模式 | 触发条件 | 事件监听器 |
|---|---|---|
永久 (permanent: true) | 图层添加时立即显示 | add, remove, move 事件 |
| 临时(默认) | 用户交互 | pointerover, pointerout, click, focus, blur, move, remove |
临时提示框事件绑定:
pointerover -> _openTooltip()
pointerout -> closeTooltip()
click -> _openTooltip()
focus -> _openTooltip() (通过 _addFocusListeners)
blur -> closeTooltip()辅助功能
提示框实现 ARIA 属性以支持屏幕阅读器辅助功能:
- Role 属性:容器具有
role="tooltip"src/layer/Tooltip.js146 - 唯一 ID:每个提示框获得
id="leaflet-tooltip-{stamp}"src/layer/Tooltip.js147 - aria-describedby:绑定的图层元素引用提示框 ID src/layer/Tooltip.js414-417
图层绑定 API
弹窗和提示框都通过绑定方法扩展 Layer 类,将覆盖层附加到图层并管理交互事件。
绑定方法
事件处理程序注册
当调用 bindPopup() 或 bindTooltip() 时,图层注册事件处理程序来管理覆盖层可见性。
弹窗事件处理程序:
click -> _openPopup()
keypress -> _onKeyPress() (在 Enter 键上打开)
remove -> closePopup()
move -> _movePopup()提示框事件处理程序(非永久):
pointerover -> _openTooltip()
pointerout -> closeTooltip()
click -> _openTooltip()
focus -> _openTooltip() (通过 _addFocusListeners)
blur -> closeTooltip()
pointermove -> _moveTooltip() (如果 sticky: true)基于函数的内容
两个覆盖层都支持接收源图层作为参数的函数内容,实现基于图层属性的动态内容。
// 来自测试套件的示例
marker.description = 'I am a marker.';
marker.bindPopup(layer => layer.description);
// 带多个图层的 FeatureGroup
const group = new FeatureGroup([marker1, marker2]);
group.bindTooltip(layer => `Tooltip: ${layer.options.description}`);实现:
- 内容函数在
_updateContent()期间调用 src/layer/DivOverlay.js275 - 源图层作为参数传递 src/layer/DivOverlay.js275
- 结果渲染为 HTML 或 DOM 元素 src/layer/DivOverlay.js277-284
定位和锚定
覆盖层相对于其地理坐标定位,具有来自源图层的可选偏移和锚点。
位置计算流程
锚点
图层提供锚点,指定覆盖层应相对于图层视觉表示定位的位置。
| 图层类型 | 锚点方法 | 用途 |
|---|---|---|
Marker | _getPopupAnchor() | 在图标的弹窗锚点位置定位 |
Marker | _getTooltipAnchor() | 在图标的提示框锚点位置定位 |
Path | (无锚点方法) | 使用默认 [0, 0] 锚点 |
默认行为:
- 标记的弹窗锚点:
icon.options.popupAnchorsrc/layer/Popup.js335-338 - 提示框锚点:非 sticky 使用
icon.options.tooltipAnchorsrc/layer/Tooltip.js220-223 - 回退:如果图层不提供锚点则使用
[0, 0]
偏移系统
两个覆盖层都支持像素偏移以微调定位:
- 弹窗偏移:默认
[0, 7](锚点下方一点)src/layer/Popup.js58-60 - 提示框偏移:默认
[0, 0](在锚点处)src/layer/Tooltip.js64-66 - 自定义偏移:作为
Point或[x, y]数组传递
事件系统
弹窗和提示框在打开或关闭时在地图和源图层上触发事件。
事件流程图
事件类型
| 事件 | 触发于 | 载荷 | 何时触发 |
|---|---|---|---|
popupopen | Map, Layer | {popup: Popup} | 弹窗添加到地图 |
popupclose | Map, Layer | {popup: Popup} | 弹窗从地图移除 |
autopanstart | Map | {...} | 自动平移开始 |
tooltipopen | Map, Layer | {tooltip: Tooltip} | 提示框添加到地图 |
tooltipclose | Map, Layer | {tooltip: Tooltip} | 提示框从地图移除 |
contentupdate | DivOverlay | {...} | 内容更新 |
事件传播
图层事件通过事件父级向上传播:
- 覆盖层在自身上触发事件
- 事件在源图层上以
propagate: true触发 src/layer/Popup.js163 src/layer/Tooltip.js106 - 事件冒泡到图层的事件父级(如果设置)
- 地图也直接接收事件 src/layer/Popup.js156 src/layer/Tooltip.js97
地图 API 方法
LeafletMap 类提供处理弹窗和提示框的便捷方法。
地图扩展
实现:
map.openPopup()通过_initOverlay()创建弹窗(如果需要)src/layer/Popup.js361-366- 自动关闭由
popup.openOn()处理 src/layer/Popup.js138-147 - 当前弹窗存储在
map._popupsrc/layer/Popup.js144 - 地图选项
closePopupOnClick默认为truesrc/layer/Popup.js348-350
使用模式
独立覆盖层
独立于图层创建和定位覆盖层:
// 带位置和选项的弹窗
const popup = new Popup(latlng, {content: 'Hello world!'})
.openOn(map);
// 带流畅 API 的提示框
const tooltip = new Tooltip()
.setLatLng(latlng)
.setContent('Tooltip text')
.addTo(map);绑定到标记
最常见的模式将覆盖层绑定到具有自动定位的标记:
// 绑定弹窗到标记
marker.bindPopup('Popup content').openPopup();
// 带选项绑定提示框
marker.bindTooltip('Tooltip text', {
direction: 'top',
permanent: true
});切换行为:点击绑定弹窗的标记会切换其打开/关闭状态 src/layer/Popup.js478-498
绑定到矢量图层
路径(折线、多边形)支持在点击点或路径中心定位的弹窗/提示框:
const polygon = new Polygon(coordinates);
polygon.bindPopup('Polygon info');
polygon.bindTooltip('Hover tooltip');矢量图层不会在点击时切换弹窗 - 它们打开弹窗并阻止地图点击传播 src/layer/Popup.js282-299
绑定到 FeatureGroups
FeatureGroups 在多个图层之间共享单个弹窗/提示框,内容基于交互的图层动态变化:
const group = new FeatureGroup([marker1, marker2]);
group.bindTooltip(layer => `Info for ${layer.options.name}`);当组中的图层被点击/悬停时,覆盖层的 _source 被设置为该特定图层 src/layer/DivOverlay.js236-248
防止地图拖拽干扰
提示框在打开前检查地图是否正在拖拽,以避免指针移动快于地图时闪烁:
// _openTooltip 中的内部逻辑
if (this._map.dragging?.moving()) {
// 如果图层刚添加则延迟打开到 moveend
if (e.type === 'add' && !this._moveEndOpensTooltip) {
this._moveEndOpensTooltip = true;
this._map.once('moveend', () => {
this._moveEndOpensTooltip = false;
this._openTooltip(e);
});
}
return;
}动态内容更新
内容可以在绑定后使用 setContent() 或通过重新绑定来更新:
// 在现有弹窗上更新内容
layer.setPopupContent('New content');
// 通过弹窗实例更新
const popup = layer.getPopup();
popup.setContent('Updated');
// 强制重新渲染
popup.update();update() 方法重新计算内容、布局和位置 src/layer/DivOverlay.js181-193