经纬志
首页 / Leaflet / 教程

事件与交互

先看结论

Leaflet 所有对象都继承同一套事件接口:on、off、once。 难点不在绑定,而在分辨事件从哪来、会不会冒泡、触发频率有多高。

最后验证 2026-11-03 · Leaflet 1.9.4 · 更新 2026-10-27 · 引用本文

事件对象里有什么

字段含义
type事件名,例如 click
target触发事件的对象,可能是地图,也可能是某个图层
latlng鼠标位置对应的地理坐标,鼠标类事件才有
layerPoint相对于地图图层原点的像素坐标
containerPoint相对于地图容器的像素坐标,做悬浮面板定位时用这个
originalEvent原生 DOM 事件,可取 shiftKey、button 等
layerlayeradd 一类事件里被增删的图层
popuppopupopen / 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)。