События мыши

Механизм событий в Leaflet построен на модели подписки/оповещения, где практически любой объект (карта, слой, маркер, геометрия) может генерировать и обрабатывать события. Мышиные события являются одним из центральных элементов взаимодействия, поскольку обеспечивают связь между действиями пользователя и географическими объектами.

События мыши распространяются как на контейнер карты, так и на отдельные векторные и растровые слои, при этом поведение зависит от типа слоя, его опций и уровня вложенности в дерево слоёв.


Базовые события мыши

Leaflet использует унифицированный набор DOM-событий, которые пробрасываются через собственную систему событий:

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

  • click — одиночный клик
  • dblclick — двойной клик
  • mousedown — нажатие кнопки мыши
  • mouseup — отпускание кнопки мыши
  • mouseover — наведение курсора
  • mouseout — уход курсора
  • mousemove — перемещение курсора
  • contextmenu — вызов контекстного меню (правый клик)

Эти события доступны как для карты, так и для интерактивных слоёв (markers, polylines, polygons, GeoJSON-объекты).


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

Каждое событие в Leaflet передаёт объект с расширенной информацией:

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

Пример обработки:

map.on('click', function (e) {
    console.log(e.latlng);
    console.log(e.containerPoint);
    console.log(e.originalEvent);
});

События карты (Map events)

Карта является глобальным объектом, который принимает события независимо от наличия слоёв.

Примеры:

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

map.on('mousemove', function (e) {
    console.log('Движение мыши:', e.latlng);
});

Особенности:

  • mousemove вызывается очень часто и требует оптимизации
  • click зависит от точности определения интерактивного слоя
  • события могут срабатывать даже при отсутствии объектов

События слоёв

Векторные слои (маркер, линия, полигон) имеют собственную обработку событий.

Marker

const marker = L.marker([51.5, -0.09]).addTo(map);

marker.on('click', function (e) {
    console.log('Клик по маркеру');
});

Polygon

const polygon = L.polygon([
    [51.51, -0.08],
    [51.5, -0.06],
    [51.52, -0.05]
]).addTo(map);

polygon.on('mouseover', function () {
    console.log('Наведение на полигон');
});

Polyline

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

polyline.on('click', function (e) {
    console.log(e.latlng);
});

Интерактивность слоёв

Для обработки мышиных событий слой должен быть интерактивным. Это регулируется опцией:

const circle = L.circle([51.508, -0.11], {
    radius: 500,
    interactive: true
}).addTo(map);

При отключённой интерактивности события мыши не срабатывают, даже если объект визуально присутствует.


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

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

Пример:

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

marker.on('click', function () {
    console.log('Клик по маркеру');
});

При клике на маркер событие может быть обработано и маркером, и картой.

Контроль поведения:

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

Управление поведением DOM-событий

Для тонкой настройки используется L.DomEvent:

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

L.DomEvent.stopPropagation(e);

Отмена стандартного поведения

L.DomEvent.preventDefault(e);

Комбинированное подавление

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

Распространённые сценарии обработки мыши

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

const polygon = L.polygon(coords).addTo(map);

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

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

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

Механизм drag основан на мышиных событиях:

const marker = L.marker([51.5, -0.09], {
    draggable: true
}).addTo(map);

marker.on('dragstart', function () {
    console.log('Начало перетаскивания');
});

marker.on('drag', function (e) {
    console.log(e.latlng);
});

marker.on('dragend', function () {
    console.log('Конец перетаскивания');
});

Работа с координатами курсора

map.on('mousemove', function (e) {
    const lat = e.latlng.lat;
    const lng = e.latlng.lng;
});

Также доступны экранные координаты:

map.on('mousemove', function (e) {
    console.log(e.containerPoint);
});

Обработка mousemove и производительность

Событие mousemove генерируется с высокой частотой, что требует оптимизации обработки.

Типичный подход — троттлинг:

let lastTime = 0;

map.on('mousemove', function (e) {
    const now = Date.now();
    if (now - lastTime < 50) return;
    lastTime = now;

    console.log(e.latlng);
});

Контекстное меню

map.on('contextmenu', function (e) {
    console.log('Правый клик:', e.latlng);
});

Подавление стандартного меню браузера:

map.on('contextmenu', function (e) {
    L.DomEvent.preventDefault(e.originalEvent);
});

События GeoJSON-слоёв

При использовании GeoJSON события назначаются через onEachFeature:

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

        layer.on('mouseover', function () {
            layer.setStyle({ weight: 5 });
        });

        layer.on('mouseout', function () {
            layer.setStyle({ weight: 2 });
        });
    }
}).addTo(map);

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

Карта может перехватывать события, даже если они произошли на слоях:

map.on('click', function (e) {
    console.log('Глобальный обработчик:', e.latlng);
});

При необходимости можно различать источник:

map.on('click', function (e) {
    if (e.originalEvent) {
        console.log('DOM-событие');
    }
});

Поведение mouseover и mouseout

Эти события чувствительны к пересечению границ объектов и могут вызываться многократно при сложной геометрии.

Особенности:

  • polygon с множеством сегментов может генерировать частые переходы
  • при пересечении внутренних частей объекта возможны ложные срабатывания
  • часто требуется логика “entered/exited state”

Пример стабилизации:

let hovered = false;

polygon.on('mouseover', function () {
    if (hovered) return;
    hovered = true;
    polygon.setStyle({ color: 'green' });
});

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

Координация событий между слоями

При наложении объектов события могут перехватываться верхним слоем. Управление осуществляется через:

  • порядок добавления слоёв
  • pane
  • опцию interactive

Пример управления слоями:

const pane = map.createPane('topPane');
pane.style.zIndex = 650;

L.circle([51.5, -0.09], {
    pane: 'topPane'
}).addTo(map);

Типичные ошибки обработки мышиных событий

  • использование mousemove без ограничений
  • отсутствие stopPropagation при конфликте слоёв
  • включение интерактивности для всех слоёв без необходимости
  • дублирование обработчиков на карте и слоях
  • игнорирование originalEvent, приводящее к потере контекста DOM

Комбинирование событий для сложного взаимодействия

Часто мышиные события используются вместе для построения логики рисования или редактирования:

map.on('mousedown', startDraw);

map.on('mousemove', updateDraw);

map.on('mouseup', finishDraw);

Такая схема формирует базу для кастомных инструментов редактирования геометрии и интерактивных картографических редакторов.