События векторных слоев

Векторные слои в Leaflet основаны на системе событий, унаследованной от ядра библиотеки и тесно связанной с DOM-подобной моделью обработки взаимодействий. Любой объект, наследующийся от L.Layer и реализующий интерактивность (например, L.Path, L.Polygon, L.Polyline, L.Circle, L.Rectangle, L.GeoJSON), поддерживает единый набор событий и механизм подписки через on, off и once.


Архитектура событий векторных объектов

Векторные объекты в Leaflet состоят из двух уровней:

  • Геометрический уровень — координаты, форма, топология
  • Интерактивный уровень — обработка пользовательских событий (мышь, касания)

Интерактивность активируется через свойство:

interactive: true

Без него события мыши для слоя не генерируются.


Подписка на события

Базовый механизм работы с событиями реализуется через методы:

  • on(type, handler)
  • off(type, handler)
  • once(type, handler)

Пример базовой подписки

const polygon = L.polygon([
  [51.5, -0.09],
  [51.51, -0.1],
  [51.52, -0.12]
]).addTo(map);

polygon.on('click', function (e) {
  console.log('Клик по полигону');
});

Одноразовое событие

polygon.once('click', function () {
  console.log('Сработает только один раз');
});

Основные события векторных слоёв

События мыши

Все интерактивные векторные слои поддерживают единый набор событий:

  • click
  • dblclick
  • mousedown
  • mouseup
  • mouseover
  • mouseout
  • mousemove
  • contextmenu

click

Срабатывает при клике по геометрии:

polygon.on('click', (e) => {
  console.log(e.latlng);
});

mouseover / mouseout

Используются для визуальной реакции при наведении:

polygon.on('mouseover', () => {
  polygon.setStyle({ color: 'red' });
});

polygon.on('mouseout', () => {
  polygon.setStyle({ color: 'blue' });
});

mousemove

Генерируется при перемещении курсора внутри слоя:

polygon.on('mousemove', (e) => {
  console.log(e.latlng);
});

Структура объекта события

Каждое событие содержит унифицированный объект event, включающий координаты и ссылки на слои.

Основные поля события

  • latlng — географические координаты
  • layerPoint — координаты в пикселях относительно слоя
  • containerPoint — координаты относительно контейнера карты
  • target — слой, вызвавший событие
  • sourceTarget — исходный источник события
  • originalEvent — нативное DOM-событие

Пример использования данных события

polygon.on('click', function (e) {
  console.log(e.latlng.lat, e.latlng.lng);
  console.log(e.containerPoint.x, e.containerPoint.y);
});

События добавления и удаления слоёв

Векторные слои генерируют системные события жизненного цикла:

  • add — слой добавлен на карту
  • remove — слой удалён с карты
polygon.on('add', () => {
  console.log('Слой добавлен');
});

polygon.on('remove', () => {
  console.log('Слой удалён');
});

Эти события полезны для инициализации ресурсов и очистки состояния.


События всплывающих окон и тултипов

Если к векторному слою привязаны popup или tooltip, доступны дополнительные события:

  • popupopen
  • popupclose
  • tooltipopen
  • tooltipclose
polygon.bindPopup('Информация');

polygon.on('popupopen', () => {
  console.log('Popup открыт');
});

polygon.on('popupclose', () => {
  console.log('Popup закрыт');
});

ГеоJSON и события на уровне фич

L.GeoJSON расширяет систему событий, добавляя обработку на уровне каждой геометрической сущности.

L.geoJSON(data, {
  onEachFeature: function (feature, layer) {
    layer.on('click', function () {
      console.log(feature.properties);
    });
  }
}).addTo(map);

Каждая feature становится независимым источником событий.


Делегирование событий

При работе с большим количеством объектов применяется паттерн делегирования:

const geoLayer = L.geoJSON(data, {
  onEachFeature: (feature, layer) => {
    layer.on('click', (e) => {
      console.log(feature.id);
    });
  }
});

Это снижает нагрузку по сравнению с массовой подпиской на уровне карты.


Всплытие событий (event bubbling)

Leaflet поддерживает механизм всплытия событий от слоя к карте.

map.on('click', (e) => {
  console.log('Клик по карте');
});

Если клик происходит по векторному слою, событие может всплыть на карту, если не остановлено.

Остановка всплытия:

polygon.on('click', function (e) {
  L.DomEvent.stopPropagation(e);
});

Настройка поведения событий

bubblingMouseEvents

Позволяет управлять передачей событий вверх:

const circle = L.circle([51.5, -0.09], {
  bubblingMouseEvents: false
});

При false события не поднимаются к карте.


Поведение интерактивности

Ключевые условия генерации событий:

  • interactive: true
  • слой добавлен на карту
  • карта имеет включённые обработчики событий
  • отсутствуют перекрывающие слои с более высоким z-index и перехватом событий

Изменение стиля через события

События часто используются для динамического изменения отображения:

const polyline = L.polyline([[51.5, -0.1], [51.51, -0.12]]).addTo(map);

polyline.on('mouseover', () => {
  polyline.setStyle({ weight: 8 });
});

polyline.on('mouseout', () => {
  polyline.setStyle({ weight: 3 });
});

Контекстное меню (contextmenu)

Позволяет обрабатывать правый клик:

polygon.on('contextmenu', (e) => {
  console.log('Контекстное меню', e.latlng);
});

Особенности событий на сложных слоях

Circle и CircleMarker

События работают аналогично полигонам, но геометрия упрощена до радиуса:

L.circle([51.5, -0.09], { radius: 500 })
  .on('click', (e) => {
    console.log('Клик по кругу');
  })
  .addTo(map);

Polyline и Polygon

Эти слои генерируют события на всей длине/площади. При этом hit detection зависит от параметров weight и lineCap.


Удаление обработчиков событий

function handler(e) {
  console.log('click');
}

polygon.on('click', handler);
polygon.off('click', handler);

Удаление критично при динамическом управлении слоями.


Массовое управление событиями

Можно отключить все события одного типа:

polygon.off('click');

Или все события слоя:

polygon.off();

Практика работы с состояниями через события

События часто используются как триггеры для изменения состояния приложения:

let selected = null;

function selectLayer(layer) {
  if (selected) {
    selected.setStyle({ color: 'blue' });
  }

  selected = layer;
  selected.setStyle({ color: 'green' });
}

geoLayer.eachLayer(layer => {
  layer.on('click', () => selectLayer(layer));
});

Взаимодействие событий и производительности

При большом количестве векторных объектов важно учитывать:

  • обработчики на каждый слой увеличивают нагрузку
  • предпочтительно использовать GeoJSON + onEachFeature
  • избегать тяжёлых операций в mousemove
  • минимизировать перерасчёт стилей внутри событий

События drag (для некоторых векторных объектов)

При включённой опции draggable (через плагины или расширения) возможны события:

  • dragstart
  • drag
  • dragend

Пример:

marker.on('dragend', (e) => {
  console.log(e.target.getLatLng());
});