事件与交互
Leaflet 所有对象都继承同一套事件接口:on、off、once。
难点不在绑定,而在分辨事件从哪来、会不会冒泡、触发频率有多高。
事件对象里有什么
| 字段 | 含义 |
|---|---|
type | 事件名,例如 click |
target | 触发事件的对象,可能是地图,也可能是某个图层 |
latlng | 鼠标位置对应的地理坐标,鼠标类事件才有 |
layerPoint | 相对于地图图层原点的像素坐标 |
containerPoint | 相对于地图容器的像素坐标,做悬浮面板定位时用这个 |
originalEvent | 原生 DOM 事件,可取 shiftKey、button 等 |
layer | layeradd 一类事件里被增删的图层 |
popup | popupopen / popupclose 里的弹窗对象 |
冒泡:点击标记也会触发地图 click
图层上的鼠标事件默认向地图冒泡。点击一个标记时,标记的 click 先执行,
接着地图的 click 也会执行。如果地图点击负责「添加点」,用户每点一次标记就会多出一个点。
在图层回调里阻止冒泡:
marker.on('click', function (e) {
L.DomEvent.stopPropagation(e);
console.log('只处理标记点击');
});
也可以在构造图层时设 bubblingMouseEvents: false,
对矢量图层和标记都适用。注意这会影响该图层上的所有鼠标事件,改动前确认没有别处依赖冒泡。
高频事件要节流
move、zoom、mousemove 在交互过程中每帧触发。
在里面做 DOM 查询、重新计算几千个要素、发网络请求,地图会立刻掉帧。三种处理方式:
- 只关心结果时改用
moveend、zoomend。 - 需要跟随时用
requestAnimationFrame合并到每帧一次。 - 只是展示坐标时直接写
textContent,不要重建节点。
var pending = false;
map.on('move', function () {
if (pending) return;
pending = true;
requestAnimationFrame(function () {
pending = false;
var c = map.getCenter();
readout.textContent = c.lat.toFixed(5) + ', ' + c.lng.toFixed(5);
});
});
图层生命周期
地图会广播 layeradd 和 layerremove,
可以用来同步外部面板的勾选状态,不需要在每个按钮里重复维护。
once 适合只执行一次的初始化逻辑,省掉手动 off。
map.on('layeradd', function (e) {
if (e.layer === overlay) console.log('专题层已显示');
});
map.once('load', function () {
console.log('首屏瓦片就绪');
});
鼠标之外:键盘与触摸
标记默认可以通过 Tab 聚焦,回车触发点击,这是 keyboard: true 的默认行为。
自定义 divIcon 时如果不给容器加 role 和可访问名称,
屏幕阅读器读到的只是一个空 div。地图缩放按钮自带 aria-label,
自己写的控件也应该补上。
移动端 Leaflet 会用 Pointer Events 统一处理单指拖动与双指缩放,
不需要额外绑定 touch 事件。需要区分「拖动结束后的点击」时,
用 click 即可,Leaflet 会抑制拖拽产生的误点击。
不要在地图容器上再绑一套 DOM 事件。容器内部有拖拽、缩放、键盘处理,
外层监听 click 容易和 Leaflet 的合成事件重复执行。
优先用 Leaflet 自己的事件接口。
解绑与内存
单页应用里销毁地图前调用 map.remove(),它会解绑所有事件并移除 DOM。
只把容器 innerHTML 清空,事件监听仍留在旧对象上,反复创建会累积。
给图层绑定的匿名函数无法单独 off,需要重复解绑时把回调存成具名函数。
经纬志(2026)。《事件与交互》。https://www.jingweizhi.com/leaflet/tutorial/events.html(访问日期 2026-11-03)。