Mapbox GL JS представляет собой событийно-ориентированную библиотеку. Практически любое действие пользователя, изменение состояния карты, загрузка ресурсов или взаимодействие со слоями генерирует события. В сложных приложениях одновременно могут происходить десятки событий, поэтому понимание их приоритета и порядка выполнения становится важной частью архитектуры интерфейса.
Под приоритетом событий обычно понимается порядок их возникновения и обработки в рамках жизненного цикла карты. Хотя библиотека не предоставляет механизма явного назначения приоритетов обработчикам, последовательность вызова событий подчиняется строгим правилам внутреннего движка.
Большинство объектов Mapbox GL JS наследуются от базового класса
Evented.
К таким объектам относятся:
MapMarkerPopupGeolocationControlПодписка выполняется через метод:
map.on('click', () => {
console.log('Клик по карте');
});
Удаление обработчика:
function handleClick() {
console.log('Клик');
}
map.on('click', handleClick);
map.off('click', handleClick);
Однократная подписка:
map.once('load', () => {
console.log('Карта загружена');
});
При анализе приоритетов необходимо помнить, что порядок выполнения
начинается именно с механизма Evented.
Если несколько обработчиков подписаны на одно событие, они выполняются в порядке регистрации.
Пример:
map.on('click', () => {
console.log('Первый');
});
map.on('click', () => {
console.log('Второй');
});
map.on('click', () => {
console.log('Третий');
});
Результат:
Первый
Второй
Третий
Таким образом, среди обработчиков одного события наивысший приоритет имеет обработчик, зарегистрированный раньше остальных.
Одной из наиболее важных последовательностей является цикл инициализации карты.
Создание экземпляра:
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [37.6173, 55.7558],
zoom: 10
});
После этого начинают возникать различные события.
Упрощённый порядок:
style.load
dataloading
sourcedataloading
data
sourcedata
load
idle
Каждое следующее событие появляется только после выполнения определённого этапа загрузки.
Событие style.load возникает после полной загрузки стиля
карты.
map.on('style.load', () => {
console.log('Стиль загружен');
});
Именно на этом этапе безопасно:
Пример:
map.on('style.load', () => {
map.addSource('cities', {
type: 'geojson',
data: 'cities.geojson'
});
});
Если попытаться добавить источник раньше, возникнет ошибка.
Следовательно, style.load имеет более высокий
фактический приоритет по сравнению с большинством операций над
слоями.
Событие load считается одним из главных событий
жизненного цикла карты.
map.on('load', () => {
console.log('Карта готова');
});
Оно возникает после:
Типичная последовательность:
style.load
↓
load
↓
idle
Поэтому код, которому требуется полностью работоспособная карта, обычно размещается именно здесь.
idle возникает тогда, когда карта больше не выполняет
фоновых операций.
map.on('idle', () => {
console.log('Все данные загружены');
});
К моменту его возникновения:
Порядок:
load
↓
render
↓
idle
Поэтому idle фактически обладает самым низким
приоритетом среди событий загрузки, но предоставляет наиболее полную
информацию о готовности карты.
Mapbox GL JS активно использует события загрузки данных.
Основные из них:
dataloading
data
sourcedataloading
sourcedata
styledataloading
styledata
Последовательность выглядит следующим образом:
styledataloading
↓
styledata
↓
sourcedataloading
↓
sourcedata
↓
load
Пример отслеживания:
map.on('sourcedataloading', () => {
console.log('Источник загружается');
});
map.on('sourcedata', () => {
console.log('Источник загружен');
});
События загрузки всегда предшествуют соответствующим событиям завершения.
Для одного действия пользователя может генерироваться несколько событий.
Например:
mousedown
mouseup
click
Последовательность возникновения:
map.on('mousedown', () => {
console.log('Нажатие');
});
map.on('mouseup', () => {
console.log('Отпускание');
});
map.on('click', () => {
console.log('Клик');
});
Результат:
Нажатие
Отпускание
Клик
Следовательно:
mousedown > mouseup > click
Здесь знак > означает более раннее возникновение
события.
Двойной щелчок формируется из нескольких событий.
Типичный порядок:
mousedown
mouseup
click
mousedown
mouseup
click
dblclick
Пример:
map.on('click', () => {
console.log('click');
});
map.on('dblclick', () => {
console.log('dblclick');
});
Событие dblclick всегда возникает позже обычного
клика.
Во время движения курсора возникают:
mouseenter
mousemove
mouseleave
Порядок выглядит так:
mouseenter
↓
mousemove
↓
mouseleave
Пример:
map.on('mouseenter', 'buildings', () => {
console.log('Вход');
});
map.on('mousemove', 'buildings', () => {
console.log('Движение');
});
map.on('mouseleave', 'buildings', () => {
console.log('Выход');
});
На мобильных устройствах используется отдельная группа событий.
Основные:
touchstart
touchmove
touchend
Пример:
map.on('touchstart', () => {
console.log('Начало касания');
});
map.on('touchmove', () => {
console.log('Перемещение');
});
map.on('touchend', () => {
console.log('Завершение');
});
Порядок строго соответствует последовательности действий пользователя.
Когда пользователь начинает перемещать карту, возникает целая цепочка событий.
Упрощённый порядок:
movestart
move
move
move
...
moveend
Пример:
map.on('movestart', () => {
console.log('Начало движения');
});
map.on('move', () => {
console.log('Движение');
});
map.on('moveend', () => {
console.log('Завершение движения');
});
Приоритет:
movestart
↓
move
↓
moveend
Обработчик одного события способен инициировать другое событие.
Пример:
map.on('click', () => {
map.fire('custom');
});
map.on('custom', () => {
console.log('Пользовательское событие');
});
При клике:
click
↓
custom
Внутренний вызов fire() создаёт вложенную цепочку
выполнения.
Во время изменения масштаба используются:
zoomstart
zoom
zoomend
Пример:
map.on('zoomstart', () => {
console.log('Начало');
});
map.on('zoom', () => {
console.log('Изменение');
});
map.on('zoomend', () => {
console.log('Конец');
});
Последовательность:
zoomstart
↓
zoom
↓
zoomend
Изменение масштаба часто сопровождается изменением положения карты.
В результате возникает сложная цепочка:
movestart
zoomstart
move
zoom
move
zoom
zoomend
moveend
Точный порядок зависит от типа взаимодействия и текущего состояния карты.
Поэтому в производственных проектах рекомендуется не предполагать жёсткую последовательность между событиями разных категорий.
Для вращения используются:
rotatestart
rotate
rotateend
Пример:
map.on('rotatestart', () => {
console.log('Старт');
});
map.on('rotate', () => {
console.log('Поворот');
});
map.on('rotateend', () => {
console.log('Конец');
});
Иерархия полностью аналогична масштабированию и перемещению.
При работе с трёхмерными картами:
pitchstart
pitch
pitchend
Пример:
map.on('pitchstart', () => {
console.log('Начало наклона');
});
Далее выполняются события pitch, а завершает цикл
pitchend.
Во время каждого кадра вызывается событие:
map.on('render', () => {
console.log('Кадр отрисован');
});
Особенности:
idle.Типичная последовательность:
load
↓
render
↓
render
↓
render
↓
idle
Из-за высокой частоты вызовов обработчики render должны
быть максимально лёгкими.
Mapbox GL JS позволяет подписываться на события конкретного слоя.
Пример:
map.on('click', 'buildings', (e) => {
console.log('Клик по зданию');
});
Одновременно может существовать общий обработчик:
map.on('click', () => {
console.log('Клик по карте');
});
При попадании курсора в объект слоя сначала выполняется обработчик слоя, затем общий обработчик карты.
Схема:
Layer Click
↓
Map Click
Это позволяет реализовывать локальную обработку взаимодействия без потери глобальной логики приложения.
В больших проектах одновременно могут работать:
mousemove
render
move
zoom
sourcedata
idle
Для контроля порядка обычно применяются:
Пример контроля состояния:
let loading = true;
map.on('idle', () => {
loading = false;
});
map.on('click', () => {
if (loading) {
return;
}
console.log('Обработка клика');
});
Такой подход позволяет искусственно создавать логические приоритеты поверх стандартной системы событий библиотеки.
Обобщённая последовательность наиболее важных групп событий выглядит следующим образом:
1. style.load
2. dataloading
sourcedataloading
styledataloading
3. data
sourcedata
styledata
4. load
5. render
6. Пользовательские события:
mousedown
mouseup
click
dblclick
7. События взаимодействия:
movestart
move
moveend
zoomstart
zoom
zoomend
rotatestart
rotate
rotateend
pitchstart
pitch
pitchend
8. idle
Понимание этой последовательности позволяет корректно организовывать загрузку данных, управление слоями, обработку пользовательских действий и синхронизацию сложных компонентов интерфейса на основе Mapbox GL JS.