Делегирование событий — это подход к обработке пользовательских действий, при котором обработчик назначается не каждому отдельному объекту, а общему родительскому элементу или контейнеру. В контексте Leaflet такой механизм особенно полезен при работе с большим количеством объектов на карте: маркерами, полигонами, окружностями, линиями и слоями.
Вместо создания десятков или сотен однотипных обработчиков можно организовать централизованную систему обработки событий, что упрощает поддержку кода и снижает нагрузку на приложение.
Практически все интерактивные объекты Leaflet наследуют возможности
класса L.Evented, который предоставляет механизм подписки и
генерации событий.
Основные методы:
on()
off()
once()
fire()
Пример подписки на событие:
marker.on('click', function(e) {
console.log('Маркер нажат');
});
Объекты Leaflet генерируют множество событий:
| Событие | Описание |
|---|---|
| click | Щелчок мышью |
| dblclick | Двойной щелчок |
| mousedown | Нажатие кнопки мыши |
| mouseup | Отпускание кнопки мыши |
| mousemove | Движение мыши |
| mouseover | Наведение курсора |
| mouseout | Уход курсора |
| contextmenu | Правая кнопка мыши |
| drag | Перетаскивание |
| zoom | Изменение масштаба |
При большом количестве объектов назначение обработчика каждому из них может стать неудобным.
Предположим, необходимо создать тысячу маркеров:
for (let i = 0; i < 1000; i++) {
const marker = L.marker([
randomLat,
randomLng
]).addTo(map);
marker.on('click', function() {
console.log('Клик по маркеру');
});
}
Подход работает корректно, однако обладает рядом недостатков:
Для решения подобных задач применяется делегирование событий.
Leaflet поддерживает механизм распространения событий вверх по иерархии объектов.
Когда пользователь нажимает на маркер, событие может быть обработано:
Схематично процесс выглядит следующим образом:
Marker
↓
LayerGroup
↓
FeatureGroup
↓
Map
Благодаря этому обработчик может находиться на более высоком уровне.
Наиболее распространенный способ делегирования в Leaflet основан на
использовании L.FeatureGroup.
Создание группы:
const cities = L.featureGroup().addTo(map);
Добавление маркеров:
const moscow = L.marker([55.7558, 37.6176]);
const spb = L.marker([59.9343, 30.3351]);
cities.addLayer(moscow);
cities.addLayer(spb);
Назначение единственного обработчика:
cities.on('click', function(e) {
console.log('Нажат объект');
});
Теперь любой объект внутри группы будет вызывать этот обработчик.
При делегировании важно понимать, какой именно слой вызвал событие.
Для этого используется свойство:
e.layer
Пример:
cities.on('click', function(e) {
console.log(e.layer);
});
Leaflet автоматически передает ссылку на объект, инициировавший событие.
Если был нажат маркер:
cities.on('click', function(e) {
const marker = e.layer;
console.log(marker.getLatLng());
});
Один обработчик способен обслуживать любое количество элементов.
Часто каждому объекту требуется сопоставить дополнительную информацию.
Пример:
const marker = L.marker([55.7558, 37.6176]);
marker.cityInfo = {
name: 'Москва',
population: 13000000
};
cities.addLayer(marker);
Делегированный обработчик:
cities.on('click', function(e) {
console.log(e.layer.cityInfo.name);
});
При нажатии на любой маркер можно получать связанные данные без создания отдельных обработчиков.
Особенно полезным делегирование становится при работе с GeoJSON.
Создание слоя:
const geoLayer = L.geoJSON(data);
Добавление на карту:
geoLayer.addTo(map);
Назначение обработчика:
geoLayer.on('click', function(e) {
console.log(e.layer.feature.properties);
});
Вне зависимости от количества объектов внутри GeoJSON событие обрабатывается централизованно.
Группа слоев также может выступать посредником событий.
Пример:
const group = L.layerGroup().addTo(map);
group.addLayer(
L.marker([55.7, 37.6])
);
group.addLayer(
L.marker([59.9, 30.3])
);
Подписка:
group.on('click', function(e) {
console.log('Сработала группа');
});
Следует учитывать, что FeatureGroup обладает более
развитой поддержкой событий и обычно предпочтительнее для подобных
задач.
В одном контейнере могут находиться различные типы слоев.
Например:
group.addLayer(
L.marker([55.7, 37.6])
);
group.addLayer(
L.circle([55.8, 37.7], {
radius: 500
})
);
group.addLayer(
L.polygon([
[55.7, 37.5],
[55.9, 37.6],
[55.8, 37.8]
])
);
Общий обработчик:
group.on('click', function(e) {
if (e.layer instanceof L.Marker) {
console.log('Маркер');
}
if (e.layer instanceof L.Circle) {
console.log('Окружность');
}
if (e.layer instanceof L.Polygon) {
console.log('Полигон');
}
});
Такой подход позволяет строить универсальные системы взаимодействия.
Одно из главных преимуществ делегирования заключается в поддержке объектов, добавленных после регистрации обработчика.
Сначала создается группа:
const markers = L.featureGroup().addTo(map);
Затем назначается обработчик:
markers.on('click', function(e) {
console.log('Клик');
});
Позже добавляется новый объект:
const marker = L.marker([
55.75,
37.61
]);
markers.addLayer(marker);
Никаких дополнительных действий выполнять не требуется.
Новый маркер автоматически участвует в системе событий группы.
Внутренний механизм Leaflet позволяет вручную организовывать цепочку распространения событий.
Добавление родителя:
child.addEventParent(parent);
Пример:
marker.addEventParent(group);
Теперь события маркера смогут передаваться объекту
group.
Удаление родителя:
marker.removeEventParent(group);
Подобная возможность применяется при разработке собственных расширений Leaflet.
Иногда делегирование необходимо ограничить.
Для этого используется:
L.DomEvent.stopPropagation(event);
Либо специальные настройки слоя.
Пример:
marker.on('click', function(e) {
L.DomEvent.stopPropagation(e);
});
После остановки распространения событие не достигнет родительских объектов.
Карта также может выступать конечным получателем событий.
Пример:
map.on('click', function(e) {
console.log(e.latlng);
});
Если событие всплывает от дочернего слоя, карта может его обработать.
Например:
map.on('click', function(e) {
console.log('Клик на карте');
});
При отсутствии блокировки распространения обработчик будет получать уведомления от объектов карты.
Распространенный сценарий — выделение любого объекта по щелчку.
Создание группы:
const selectable = L.featureGroup().addTo(map);
Общий обработчик:
selectable.on('click', function(e) {
selectable.eachLayer(function(layer) {
if (layer.setStyle) {
layer.setStyle({
color: '#3388ff'
});
}
});
if (e.layer.setStyle) {
e.layer.setStyle({
color: 'red'
});
}
});
Независимо от количества элементов логика выбора сосредоточена в одном месте.
При работе с большим объемом данных преимущества становятся особенно заметны.
Сравнение подходов:
marker.on('click', handler);
Для каждой сущности создается собственная подписка.
group.on('click', handler);
Для всей коллекции существует единая точка обработки.
Преимущества:
Создание интерактивного слоя городов:
const cities = L.featureGroup().addTo(map);
const data = [
{
name: 'Москва',
coords: [55.7558, 37.6176]
},
{
name: 'Санкт-Петербург',
coords: [59.9343, 30.3351]
},
{
name: 'Казань',
coords: [55.7961, 49.1064]
}
];
Добавление объектов:
data.forEach(city => {
const marker = L.marker(city.coords);
marker.cityName = city.name;
cities.addLayer(marker);
});
Делегированный обработчик:
cities.on('click', function(e) {
L.popup()
.setLatLng(
e.layer.getLatLng()
)
.setContent(
e.layer.cityName
)
.openOn(map);
});
Количество городов может изменяться динамически, а логика взаимодействия остается сосредоточенной в одном обработчике.
Использовать FeatureGroup для интерактивных
наборов объектов.
const group = L.featureGroup();
Хранить общую логику обработки в одном месте.
group.on('click', handler);
Передавать пользовательские данные через свойства объекта или GeoJSON-свойства.
marker.metadata = {...};
Проверять тип слоя перед выполнением специфических операций.
if (e.layer instanceof L.Marker) {
...
}
Применять делегирование для больших коллекций объектов и динамически изменяемых данных.
Такой подход обеспечивает масштабируемую архитектуру картографического приложения и позволяет эффективно управлять событиями независимо от количества отображаемых слоев.