Всплытие событий

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

Всплытие событий (event bubbling) представляет собой механизм, при котором событие, возникшее на дочернем элементе, последовательно поднимается вверх по цепочке объектов, пока не достигнет корневого обработчика или не будет остановлено явно.


Базовая модель событий в Google Maps JavaScript API

Событийная модель в API Google Maps построена поверх стандартной модели DOM, но имеет собственную реализацию диспетчеризации событий. Основной механизм основан на классе google.maps.MVCObject, от которого наследуются ключевые сущности API: Map, Marker, Polyline, Polygon, InfoWindow и другие.

Каждый объект, основанный на MVCObject, поддерживает регистрацию слушателей через:

google.maps.event.addListener(instance, eventName, handler);

или более современный подход:

instance.addListener(eventName, handler);

События в Google Maps API не всегда строго следуют DOM-модели bubbling/capturing, однако концептуально многие из них реализуют аналогичную иерархическую передачу.


Отличие событий карты от DOM-событий

DOM-события, например клики по кнопке, проходят фазу:

  1. Capturing (перехват сверху вниз)
  2. Target (целевой элемент)
  3. Bubbling (всплытие снизу вверх)

В Google Maps JavaScript API модель проще и ближе к dispatch-based системе: событие генерируется объектом и может быть обработано слушателями этого объекта и иногда глобальными слушателями карты.

Пример DOM:

document.body.addEventListener("click", () => {
    console.log("body click");
});

Пример Maps API:

const marker = new google.maps.Marker({
    position: { lat: 50, lng: 30 },
    map: map
});

marker.addListener("click", () => {
    console.log("marker click");
});

Здесь событие «click» возникает не в DOM, а внутри слоя карты и обрабатывается системой событий API.


Концепция всплытия в контексте карты

Всплытие событий в картографическом интерфейсе проявляется как передача события от геометрического объекта к карте. Например:

  • клик по маркеру
  • клик по полигону
  • клик по тайлу карты

Каждое из этих событий может быть обработано:

  1. На уровне конкретного объекта (Marker, Polygon)
  2. На уровне карты (Map)
  3. Через глобальные слушатели событий

Клик по маркеру и его поведение

При клике на маркер происходит генерация события click. Оно не распространяется в DOM, но логически может быть интерпретировано как всплывающее внутри системы карты.

const marker = new google.maps.Marker({
    position: { lat: 55.75, lng: 37.61 },
    map
});

marker.addListener("click", (event) => {
    console.log("Клик по маркеру");
});

Если зарегистрировать обработчик на карте:

map.addListener("click", (event) => {
    console.log("Клик по карте");
});

При клике по маркеру обработчик карты может не сработать, если событие было перехвачено маркером. Это поведение имитирует остановку всплытия.


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

В DOM используется:

event.stopPropagation();

В Google Maps API аналогичное поведение реализуется через:

google.maps.event.addListener(marker, "click", (event) => {
    event.stop();
});

Метод stop() предотвращает дальнейшую обработку события другими слушателями, включая глобальные обработчики карты.


Иерархия объектов и логика распространения

Событийная модель можно представить как дерево:

  • Map

    • Layer

      • Marker
      • Polyline
      • Polygon

При возникновении события на уровне Marker оно может быть обработано:

  1. локально (Marker listener)
  2. глобально (Map listener)

Но поведение зависит от типа события. Например, события drag, click, rightclick имеют разные правила распространения.


Пример взаимодействия нескольких уровней

map.addListener("click", (e) => {
    console.log("MAP CLICK");
});

const marker = new google.maps.Marker({
    position: { lat: 51.5, lng: -0.12 },
    map
});

marker.addListener("click", (e) => {
    console.log("MARKER CLICK");
});

При клике по маркеру:

  • выполняется MARKER CLICK
  • MAP CLICK может не выполниться

Таким образом, маркер перехватывает событие до того, как оно достигнет карты.


Использование InfoWindow и всплытие событий

InfoWindow добавляет дополнительный уровень обработки событий. При открытии окна и взаимодействии с его содержимым события снова переходят в DOM-модель.

const infoWindow = new google.maps.InfoWindow({
    content: "<div>Текст</div>"
});

marker.addListener("click", () => {
    infoWindow.open(map, marker);
});

Здесь возникает смешанная модель:

  • событие клика обрабатывается системой Maps API
  • содержимое InfoWindow работает в DOM
  • события внутри окна уже всплывают по DOM-иерархии

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

Особенность архитектуры заключается в том, что карта рендерится внутри DOM-элемента, но логика взаимодействия абстрагирована.

Например:

map.getDiv().addEventListener("click", () => {
    console.log("DOM click on map container");
});

Этот обработчик сработает независимо от событий Google Maps API, если они не остановили всплытие DOM-события.

Однако важно различать:

  • DOM click на контейнере карты
  • Google Maps click на геометрии карты

Они существуют параллельно.


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

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

const polygon = new google.maps.Polygon({
    paths: [
        { lat: 52, lng: 30 },
        { lat: 53, lng: 30 },
        { lat: 52.5, lng: 31 }
    ],
    map
});

polygon.addListener("click", (e) => {
    console.log("POLYGON CLICK");
});

При клике внутри полигона:

  • событие генерируется полигоном
  • карта может получить событие только если оно не остановлено

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

Всплытие позволяет реализовать делегирование: один обработчик на карте может обрабатывать события множества объектов.

map.addListener("click", (event) => {
    if (event.placeId) {
        console.log("Клик по месту:", event.placeId);
    }
});

Такой подход снижает количество обработчиков и упрощает управление логикой.


Управление конфликтами событий

При сложных интерфейсах возникает конфликт между слоями:

  • маркеры
  • полигоны
  • overlay-системы
  • кастомные HTML-слои

Для управления используется:

  • stop() для блокировки распространения
  • проверка event.target
  • фильтрация по координатам

Кастомные оверлеи и всплытие

При создании OverlayView разработчик получает полный контроль над DOM-слоем карты:

class CustomOverlay extends google.maps.OverlayView {
    onAdd() {
        const div = document.createElement("div");
        div.textContent = "Overlay";
        this.getPanes().overlayLayer.appendChild(div);
    }

    draw() {}
}

События внутри такого слоя снова становятся DOM-событиями и начинают всплывать по стандартным правилам браузера, а не Google Maps API.


Гибридная модель событий

Архитектура Google Maps JavaScript API сочетает три уровня:

  1. Событийная модель API (MVCObject)
  2. DOM-события контейнера карты
  3. Внутренние события браузера внутри overlay

Всплытие становится не единым механизмом, а совокупностью взаимодействующих систем.


Практическое значение всплытия

Механизм всплытия позволяет:

  • централизовать обработку событий карты
  • минимизировать количество слушателей
  • реализовывать сложные интерактивные сценарии
  • разделять уровни логики интерфейса

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