События пользовательского взаимодействия

Google Maps JavaScript API предоставляет событийную модель, построенную вокруг подписки на пользовательские и системные события карты, слоёв и объектов. Архитектура событий основана на паттерне Observer: каждый объект (карта, маркер, полигон, сервисы) генерирует события, на которые можно подписаться через addListener, обеспечивая реактивное поведение интерфейса.


События в API делятся на несколько категорий:

  • события карты (Map events)
  • события объектов (Marker, Polyline, Polygon, Circle)
  • DOM-события (клики и движения мыши)
  • события сервисов (Autocomplete, Directions, Places)
  • события состояния (изменение центра, масштаба, границ)

Базовый механизм подписки:

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

Также используется сокращённая форма:

instance.addListener(eventName, handler);

Отписка осуществляется через объект Listener:

const listener = map.addListener('click', () => {});
google.maps.event.removeListener(listener);

Событие клика по карте

Событие click является одним из наиболее часто используемых. Оно срабатывает при любом щелчке по поверхности карты.

map.addListener('click', (event) => {
  console.log(event.latLng.lat(), event.latLng.lng());
});

Объект события содержит:

  • latLng — координаты точки
  • pixel (в некоторых контекстах) — экранные координаты
  • placeId — идентификатор места (если клик по POI)

Использование placeId позволяет интегрироваться с сервисом Places:

map.addListener('click', (event) => {
  if (event.placeId) {
    event.stop();
  }
});

Метод stop() блокирует стандартное поведение открытия карточки места.


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

Карта поддерживает режим drag, генерируя последовательность событий:

  • dragstart
  • drag
  • dragend
map.addListener('dragstart', () => {
  console.log('Начало перемещения карты');
});

map.addListener('dragend', () => {
  console.log('Перемещение завершено');
});

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


Изменение центра карты

Событие center_changed возникает при любом изменении центра карты, включая анимации и перетаскивание.

map.addListener('center_changed', () => {
  const center = map.getCenter();
});

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


Изменение масштаба

Событие zoom_changed срабатывает при изменении уровня увеличения карты:

map.addListener('zoom_changed', () => {
  const zoom = map.getZoom();
});

Типичные сценарии:

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

Изменение границ отображения

Событие bounds_changed фиксирует любые изменения видимой области карты.

map.addListener('bounds_changed', () => {
  const bounds = map.getBounds();
});

LatLngBounds используется для:

  • фильтрации объектов по области
  • динамической подгрузки данных
  • серверного поиска в текущем окне карты

Событие изменения типа карты

maptypeid_changed отслеживает переключение между слоями:

  • roadmap
  • satellite
  • hybrid
  • terrain
map.addListener('maptypeid_changed', () => {
  const type = map.getMapTypeId();
});

Используется для адаптации визуальных слоёв и отключения/включения overlay-объектов.


События мыши

Карты поддерживают низкоуровневые события мыши:

  • mousemove
  • mousedown
  • mouseup
  • rightclick
  • dblclick

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

map.addListener('rightclick', (event) => {
  console.log('Контекстное меню координаты:', event.latLng.toString());
});

Событие dblclick часто используется для зума:

map.addListener('dblclick', (event) => {
  map.setZoom(map.getZoom() + 1);
});

События маркеров

Маркер генерирует собственный набор событий:

  • click
  • dragstart
  • drag
  • dragend
  • mouseover
  • mouseout
const marker = new google.maps.Marker({
  position: { lat: 40, lng: -70 },
  map
});

marker.addListener('click', () => {
  console.log('Маркер активирован');
});

Перетаскиваемые маркеры:

const marker = new google.maps.Marker({
  position: { lat: 40, lng: -70 },
  map,
  draggable: true
});

События drag позволяют реализовать редакторы геометрии, пользовательские точки интереса и интерактивные маршруты.


События информационных окон

Информационные окна (InfoWindow) поддерживают:

  • closeclick
  • domready
const info = new google.maps.InfoWindow({
  content: '<div>Точка</div>'
});

info.addListener('domready', () => {
  console.log('DOM окна готов');
});

domready используется для:

  • инициализации DOM-логики
  • подключения сторонних виджетов
  • динамического изменения содержимого

События полигонов и линий

Геометрические объекты (Polygon, Polyline, Circle) поддерживают:

  • click
  • mousemove
  • rightclick
polygon.addListener('click', (event) => {
  console.log(event.latLng);
});

Дополнительно доступны события редактирования:

  • set_at
  • insert_at
  • remove_at

при использовании MVCArray в editable-режиме:

polygon.getPath().addListener('set_at', () => {
  console.log('Точка изменена');
});

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

Некоторые события отражают внутреннее состояние рендеринга:

  • idle — карта завершила все изменения и загрузку тайлов
  • tilesloaded — завершена загрузка тайлов
map.addListener('idle', () => {
  console.log('Карта стабилизирована');
});

idle часто используется как точка синхронизации для:

  • загрузки данных по текущему состоянию
  • обновления UI после перемещений
  • батчинга запросов к серверу

События масштабируемых слоёв и оверлеев

OverlayView и кастомные слои поддерживают низкоуровневые события:

  • изменение проекции
  • перерисовка слоя
  • изменение масштаба
class CustomOverlay extends google.maps.OverlayView {
  onAdd() {}
  draw() {}
  onRemove() {}
}

События здесь часто реализуются вручную через внешние слушатели карты.


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

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

  • минимизация обработчиков mousemove
  • использование idle вместо bounds_changed
  • кэширование координат
  • удаление слушателей при уничтожении объектов

Паттерн агрегирования событий:

let timeout;

map.addListener('bounds_changed', () => {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    const bounds = map.getBounds();
  }, 200);
});

Координация событий карты и сервисов

События карты часто связаны с сервисами:

  • DirectionsService
  • PlacesService
  • Autocomplete

Пример интеграции с изменением границ:

map.addListener('idle', () => {
  const bounds = map.getBounds();

  service.nearbySearch({ bounds }, (results) => {
    console.log(results);
  });
});

Такой подход создаёт реактивную карту, синхронизированную с пользовательским взаимодействием.


События пользовательского интерфейса и DOM

Хотя API абстрагирует DOM, часть событий проходит через HTML-слой:

  • взаимодействие с кастомными controls
  • события внутри InfoWindow
  • клики по overlay-элементам

Контроль событий внутри кастомных элементов:

const controlDiv = document.createElement('div');

controlDiv.addEventListener('click', () => {
  map.setZoom(map.getZoom() + 1);
});

Приоритет и всплытие событий

События могут:

  • всплывать от объектов к карте
  • перехватываться через stop()
  • конфликтовать при перекрытии слоёв

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

map.addListener('click', (event) => {
  if (event.placeId) {
    event.stop();
  }
});

Синхронизация состояния интерфейса через события

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

  • карта → состояние приложения
  • маркеры → UI-панели
  • bounds → запросы к серверу
  • zoom → переключение визуальных режимов

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