События popup

Leaflet реализует событийную модель поверх объектов карты, слоёв и всплывающих окон, где popup рассматривается как полноценный интерактивный компонент с жизненным циклом. События popup возникают при открытии, закрытии и обновлении всплывающих окон, а также при их привязке к слоям. Эти события позволяют синхронизировать UI, управлять состоянием интерфейса и строить сложные сценарии взаимодействия с картой.

Основные события, связанные с всплывающими окнами, концентрируются вокруг их жизненного цикла:

  • popupopen — вызывается при открытии popup
  • popupclose — вызывается при закрытии popup

Эти события доступны как на уровне карты, так и на уровне слоёв, в зависимости от того, где был инициирован popup.

Пример регистрации обработчиков на карте:

map.on('popupopen', function (e) {
    console.log('Popup открыт');
    console.log(e.popup);
    console.log(e.popup._source);
});

map.on('popupclose', function (e) {
    console.log('Popup закрыт');
});

Объект события e содержит ссылку на сам popup, а также на источник, если popup был привязан к слою (marker, polygon и т.д.). Это позволяет отслеживать, какой именно объект карты инициировал всплывающее окно.

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

События popup передают унифицированный объект, в котором ключевую роль играют следующие поля:

  • popup — экземпляр popup, который был открыт или закрыт
  • target — объект, на котором произошло событие (обычно карта)
  • sourceTarget — слой, инициировавший popup
  • containerPoint / latlng — координаты, если popup привязан к географической точке

Пример использования координат:

map.on('popupopen', function (e) {
    if (e.popup && e.popup.getLatLng) {
        const coords = e.popup.getLatLng();
        console.log('Popup открыт в точке:', coords);
    }
});

Наличие latlng зависит от типа popup: он может быть привязан как к географической точке, так и к DOM-элементу без координат.

Привязка popup к слоям и влияние на события

Popup чаще всего создаётся через привязку к слою:

marker.bindPopup('Информация об объекте');

В этом случае событие popupopen срабатывает не только на карте, но и на уровне слоя:

marker.on('popupopen', function (e) {
    console.log('Popup открыт для маркера');
});

Такой подход позволяет локализовать логику обработки внутри конкретного объекта карты, не перегружая глобальные обработчики.

При этом один и тот же popup может инициировать события на нескольких уровнях:

  • слой (marker, polyline, polygon)
  • карта (map)
  • сам popup

popupopen: порядок вызова и контекст

При открытии popup происходит последовательность действий:

  1. Создание или активация popup
  2. Привязка к источнику (layer)
  3. Добавление в DOM
  4. Генерация события popupopen на слое
  5. Генерация события popupopen на карте

Контекст this внутри обработчиков зависит от способа подписки:

map.on('popupopen', function (e) {
    console.log(this === map); // true
});

При обработке на уровне слоя:

marker.on('popupopen', function (e) {
    console.log(this === marker); // true
});

popupclose и удаление из DOM

Событие закрытия popup срабатывает при:

  • ручном закрытии пользователем
  • вызове map.closePopup()
  • открытии другого popup (если используется режим одного окна)
map.on('popupclose', function (e) {
    console.log('Закрыт popup:', e.popup);
});

Важно учитывать, что после popupclose DOM-элемент может быть удалён или переиспользован, поэтому прямые ссылки на внутренние элементы popup становятся невалидными.

Управление открытием popup через события

Popup может быть открыт программно, и это также инициирует события:

marker.openPopup();

При этом popupopen сработает аналогично пользовательскому открытию. Это позволяет унифицировать обработку вне зависимости от источника действия.

Распространённый паттерн — блокировка повторного открытия:

map.on('popupopen', function (e) {
    if (e.popup && e.popup._alreadyHandled) return;

    e.popup._alreadyHandled = true;
});

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

События popup участвуют в механизме всплытия событий карты. Если popup открыт на маркере, событие проходит цепочку:

marker → layer → map

Это позволяет централизованно перехватывать события:

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

Однако при этом важно учитывать дублирование логики: один и тот же popup может быть обработан на нескольких уровнях, если подписки не разделены.

Динамическое обновление содержимого popup

При изменении содержимого popup часто используется сочетание событий и методов обновления:

map.on('popupopen', function (e) {
    const popup = e.popup;

    popup.setContent('Загрузка данных...');

    setTimeout(() => {
        popup.setContent('Обновлённые данные');
    }, 1000);
});

Такой подход используется для асинхронной подгрузки данных, когда popup уже отображён, но контент ещё формируется.

Поведение при нескольких popup

По умолчанию карта может отображать только один активный popup. При открытии нового:

  • предыдущий закрывается
  • вызывается popupclose
  • затем popupopen для нового

Это создаёт каскад событий:

map.on('popupclose', function () {
    console.log('Закрыт предыдущий popup');
});

map.on('popupopen', function () {
    console.log('Открыт новый popup');
});

При кастомной логике управления popup важно учитывать этот порядок, особенно при синхронизации состояния интерфейса.

Взаимодействие popup с событиями карты

Popup влияет на поведение карты в части обработки событий взаимодействия:

  • клик по карте может закрывать popup
  • drag карты не влияет на состояние popup
  • zoom не вызывает пересоздание popup, но может менять его позицию

События popup часто используются совместно с:

  • moveend
  • zoomend
  • click

Пример синхронизации:

map.on('popupopen', function (e) {
    map.panTo(e.popup.getLatLng());
});

Отмена стандартного поведения через события

Некоторые сценарии требуют контроля над автоматическим открытием popup:

marker.on('click', function (e) {
    e.originalEvent.preventDefault();
    marker.openPopup();
});

Хотя popup и управляется Leaflet автоматически при bindPopup, обработчики событий позволяют переопределить стандартное поведение и внедрить собственную логику условий открытия.

Особенности работы с кастомными popup

При создании кастомных popup через L.popup() события работают аналогично, но:

  • источник может отсутствовать
  • координаты могут быть заданы вручную
  • DOM структура полностью управляется разработчиком
const popup = L.popup()
    .setLatLng([51.5, -0.09])
    .setContent('Кастомный popup');

map.on('popupopen', function (e) {
    console.log('Открыт кастомный popup');
});