Обработчики load, move, zoom

В Mapbox GL JS событие load является ключевой точкой жизненного цикла карты. Оно срабатывает после полной загрузки стиля, источников данных и базовых слоёв, когда карта становится готовой к безопасной модификации.

Именно в этот момент разрешается добавление новых слоёв, источников и навешивание логики, зависящей от уже загруженной конфигурации.

mapboxgl.accessToken = 'YOUR_TOKEN';

const map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/streets-v12',
  center: [69.2401, 41.2995],
  zoom: 10
});

map.on('load', () => {
  map.addSource('points', {
    type: 'geojson',
    data: {
      type: 'FeatureCollection',
      features: []
    }
  });

  map.addLayer({
    id: 'points-layer',
    type: 'circle',
    source: 'points',
    paint: {
      'circle-radius': 6,
      'circle-color': '#3b82f6'
    }
  });
});

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

Дополнительно в контексте инициализации используется проверка состояния через map.isStyleLoaded(), позволяющая безопасно выполнять операции вне обработчика события.


Событие move и отслеживание изменения центра карты

Событие move активируется при любом изменении положения карты, включая панорамирование, инерционное движение и программные изменения через методы jumpTo, panTo, setCenter.

В отличие от событий завершения (moveend), move вызывается на каждом кадре анимации, что делает его чувствительным к нагрузке.

map.on('move', () => {
  const center = map.getCenter();
  console.log('Центр карты:', center.lng, center.lat);
});

Основной объект состояния, доступный внутри обработчика:

  • map.getCenter() — текущий центр карты
  • map.getBounds() — текущие границы отображения
  • map.getZoom() — текущий уровень масштаба
  • map.getBearing() — угол поворота карты
  • map.getPitch() — наклон камеры

При высокой частоте обновлений важно учитывать стоимость операций внутри обработчика move. Визуальные обновления DOM или сложные вычисления координат могут приводить к падению производительности.

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

let scheduled = false;

map.on('move', () => {
  if (scheduled) return;
  scheduled = true;

  requestAnimationFrame(() => {
    const center = map.getCenter();
    document.getElementById('info').textContent =
      `${center.lng.toFixed(4)}, ${center.lat.toFixed(4)}`;
    scheduled = false;
  });
});

События movestart и moveend как границы взаимодействия

Помимо move, используются события movestart и moveend, определяющие начало и завершение взаимодействия с картой.

  • movestart — активируется при начале панорамирования или анимации
  • move — вызывается непрерывно в процессе движения
  • moveend — срабатывает после стабилизации состояния
map.on('movestart', () => {
  console.log('Начало перемещения');
});

map.on('moveend', () => {
  console.log('Перемещение завершено');
  console.log('Финальный центр:', map.getCenter());
});

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


События zoom, zoomstart и zoomend

События масштабирования работают аналогично механике движения и включают три стадии:

  • zoomstart — начало изменения масштаба
  • zoom — непрерывное обновление значения zoom
  • zoomend — завершение изменения

В Mapbox GL JS масштаб представляет собой непрерывную шкалу, где каждый шаг примерно соответствует двукратному изменению масштаба.

map.on('zoom', () => {
  console.log('Zoom:', map.getZoom());
});

Внутренне значение zoom влияет на уровень детализации тайлов и отображение слоёв. При увеличении масштаба происходит подгрузка более детализированных тайлов в зависимости от доступных источников данных.

Контроль поведения при изменении zoom

Событие zoomend часто используется для обновления визуализации, которая зависит от итогового масштаба, например кластеризации или динамической фильтрации объектов.

map.on('zoomend', () => {
  const zoom = map.getZoom();

  if (zoom > 12) {
    map.setLayoutProperty('points-layer', 'visibility', 'visible');
  } else {
    map.setLayoutProperty('points-layer', 'visibility', 'none');
  }
});

Программное управление zoom и его отражение в событиях

Изменение масштаба может происходить программно:

map.zoomTo(14, { duration: 1000 });

или

map.setZoom(8);

Любое программное изменение инициирует цепочку событий zoomstart → zoom → zoomend, аналогичную пользовательскому взаимодействию. Это позволяет унифицировать логику обработки вне зависимости от источника изменения состояния.


Связь move и zoom в модели камеры

В Mapbox GL JS камера карты представляет собой совокупность параметров:

  • центр (longitude, latitude)
  • zoom
  • bearing
  • pitch

События move и zoom фактически отражают разные аспекты одной и той же камеры. Внутри архитектуры они неразрывно связаны, поскольку любое изменение zoom влияет на композицию сцены и пересчёт матрицы проекции.

map.on('move', () => {
  const state = {
    center: map.getCenter(),
    zoom: map.getZoom(),
    bearing: map.getBearing(),
    pitch: map.getPitch()
  };

  console.log(state);
});

Такой подход используется для синхронизации состояния карты с внешними системами, включая маршрутизацию интерфейсов и сохранение состояния в URL.


Отличие событий непрерывного и дискретного типа

События в Mapbox GL JS делятся на две категории:

Непрерывные:

  • move
  • zoom
  • rotate

Они вызываются на каждом кадре изменения состояния и требуют оптимизации.

Дискретные:

  • moveend
  • zoomend
  • load

Они фиксируют завершённое состояние и подходят для тяжёлых операций.

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


Поведение событий при инерции и анимациях

При инерционном перемещении карты после отпускания мыши события move и zoom продолжают срабатывать, поскольку камера продолжает изменять своё положение.

Анимации, инициированные методами flyTo или easeTo, также генерируют полный цикл событий.

map.flyTo({
  center: [69.2401, 41.2995],
  zoom: 12,
  speed: 1.2
});

map.on('moveend', () => {
  console.log('Анимация завершена');
});

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


Практика синхронизации состояния карты

При построении сложных интерфейсов состояние карты часто синхронизируется с внешним состоянием приложения.

function syncState() {
  const state = {
    center: map.getCenter().toArray(),
    zoom: map.getZoom()
  };

  history.replaceState({}, '', `#${state.center[0]},${state.center[1]},${state.zoom}`);
}

map.on('moveend', syncState);
map.on('zoomend', syncState);

Использование moveend и zoomend снижает количество обновлений и предотвращает перегрузку истории браузера.


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

События load, move и zoom формируют основу управления жизненным циклом карты в Mapbox GL JS:

  • load фиксирует готовность карты к модификации
  • move отражает непрерывное изменение положения камеры
  • zoom отслеживает изменение масштаба и детализации
  • moveend и zoomend фиксируют стабильные состояния для вычислений

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