Система событий в MapLibre GL JS является основой взаимодействия между приложением и картой. Через события реализуются реакции на действия пользователя, загрузку данных, изменения состояния карты, работу источников данных и слоёв. Однако именно события часто становятся источником трудноуловимых ошибок: обработчики не вызываются, срабатывают несколько раз, работают в неправильном порядке или приводят к утечкам памяти.
Понимание типичных проблем и способов их диагностики существенно упрощает разработку сложных картографических приложений.
Объект карты предоставляет методы регистрации обработчиков:
map.on('click', () => {
console.log('Клик по карте');
});
Удаление обработчика выполняется через:
map.off('click', handler);
Однократное выполнение:
map.once('load', () => {
console.log('Карта загружена');
});
Большинство проблем возникает из-за неправильного понимания жизненного цикла карты и порядка возникновения событий.
Одна из самых распространённых ситуаций — зарегистрированный обработчик никогда не срабатывает.
Пример:
map.on('click', () => {
console.log('Событие произошло');
});
Если сообщений в консоли нет, необходимо проверить несколько факторов.
MapLibre строго проверяет названия событий.
Ошибка:
map.on('clicked', () => {
console.log('Ошибка');
});
Правильно:
map.on('click', () => {
console.log('Работает');
});
Даже небольшая опечатка полностью отключает обработчик.
Ошибка возникает при попытке регистрации событий до создания экземпляра карты.
Неправильно:
let map;
map.on('click', () => {
console.log('Клик');
});
Правильно:
const map = new maplibregl.Map({
container: 'map',
style: 'style.json'
});
map.on('click', () => {
console.log('Клик');
});
Некоторые события возникают только один раз.
Например:
map.on('load', () => {
console.log('Загрузка завершена');
});
Если регистрация происходит после загрузки карты:
setTimeout(() => {
map.on('load', () => {
console.log('Не вызовется');
});
}, 5000);
Событие уже произошло и больше не повторится.
Для проверки состояния карты используется:
if (map.loaded()) {
console.log('Карта уже загружена');
}
Иногда обработчик неожиданно выполняется дважды, трижды или десятки раз.
Причина обычно связана с повторной регистрацией.
Пример:
function initialize() {
map.on('click', () => {
console.log('Клик');
});
}
Если функция вызывается несколько раз:
initialize();
initialize();
initialize();
Каждый вызов создаёт новый обработчик.
В результате один клик приведёт к трём сообщениям в консоли.
Полезно логировать момент подключения обработчика:
console.log('Подключение обработчика');
map.on('click', handler);
Если сообщение появляется многократно, значит регистрация происходит повторно.
Если событие должно быть обработано только один раз:
map.once('click', () => {
console.log('Первый клик');
});
После первого вызова обработчик автоматически удаляется.
Частая ошибка связана с использованием анонимных функций.
Неправильно:
map.on('click', () => {
console.log('Клик');
});
map.off('click', () => {
console.log('Клик');
});
Это разные объекты функций.
Удаление не произойдёт.
Правильно:
function clickHandler() {
console.log('Клик');
}
map.on('click', clickHandler);
map.off('click', clickHandler);
Теперь ссылка на функцию одинакова.
В больших приложениях карты могут создаваться и уничтожаться многократно.
Пример проблемы:
function createMap() {
const map = new maplibregl.Map(...);
map.on('move', () => {
console.log('Перемещение');
});
return map;
}
Если карта удаляется:
map.remove();
Но внешние ссылки на обработчики сохраняются, память может освобождаться не полностью.
Особенно это заметно в SPA-приложениях.
Рекомендуется явно удалять обработчики:
map.off('move', moveHandler);
Перед уничтожением карты.
Событие load считается одним из наиболее важных.
map.on('load', () => {
console.log('Готово');
});
Оно возникает после первоначальной загрузки стиля и ресурсов.
Однако разработчики часто ошибочно предполагают, что после него доступны абсолютно все данные.
Пример:
map.on('load', () => {
console.log('Карта готова');
});
Наличие события load не означает завершение загрузки
всех тайлов и всех сетевых запросов.
Поэтому код вида:
map.on('load', () => {
const features = map.queryRenderedFeatures();
});
Может возвращать неполные результаты.
Для ожидания полной стабилизации карты используется событие:
map.on('idle', () => {
console.log('Все операции завершены');
});
Оно вызывается тогда, когда:
Для анализа данных это часто более надёжный вариант.
MapLibre позволяет подписываться на события конкретного слоя.
Пример:
map.on('click', 'cities', (e) => {
console.log(e.features);
});
Если обработчик не работает, необходимо проверить наличие слоя.
Ошибка:
map.on('click', 'cities', handler);
До добавления слоя:
map.addLayer({
id: 'cities',
...
});
На момент регистрации слой ещё не существует.
Безопасный вариант:
map.on('load', () => {
map.addLayer(...);
map.on('click', 'cities', handler);
});
Даже один лишний символ приводит к отсутствию событий.
Например:
map.on('click', 'city', handler);
При наличии слоя:
id: 'cities'
Обработчик никогда не сработает.
Некоторые события генерируются десятки раз в секунду.
Примеры:
move
zoom
drag
render
mousemove
Неправильно:
map.on('mousemove', () => {
expensiveOperation();
});
Если курсор активно перемещается, функция может выполняться сотни раз в секунду.
Это приводит к:
Пример ограничения частоты вызовов:
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn(...args);
}, delay);
};
}
map.on('move', debounce(() => {
console.log('Карта остановилась');
}, 300));
Для регулярного контроля частоты:
function throttle(fn, limit) {
let waiting = false;
return (...args) => {
if (!waiting) {
fn(...args);
waiting = true;
setTimeout(() => {
waiting = false;
}, limit);
}
};
}
Применение:
map.on('mousemove', throttle(handler, 100));
Разработчики часто предполагают, что события происходят в определённой последовательности.
На практике порядок может зависеть от:
Полезно временно логировать события:
[
'load',
'styledata',
'sourcedata',
'render',
'idle'
].forEach(eventName => {
map.on(eventName, () => {
console.log(eventName);
});
});
Так можно увидеть реальную последовательность выполнения.
При вызове:
map.setStyle('new-style.json');
Многие разработчики ожидают сохранения обработчиков слоёв.
Однако новый стиль может полностью заменить набор слоёв.
Пример:
map.on('click', 'cities', handler);
После смены стиля слой:
cities
Может исчезнуть.
Соответственно обработчик перестанет работать.
Используется событие:
map.on('style.load', () => {
initializeLayers();
});
После загрузки нового стиля можно заново добавить слои, источники и связанные обработчики.
Для диагностики загрузки данных используются:
sourcedata
dataloading
data
Пример:
map.on('sourcedata', event => {
console.log(event);
});
Эти события помогают определить:
Практически любое событие содержит полезную информацию.
Например:
map.on('click', event => {
console.log(event);
});
Для события клика доступны:
event.lngLat
event.point
event.originalEvent
Для событий данных доступны другие свойства.
При возникновении непонятного поведения полезно сначала вывести весь объект:
console.dir(event);
Это позволяет увидеть фактическую структуру события.
При использовании классов часто возникает проблема потери контекста.
Пример:
class MapController {
constructor(map) {
this.map = map;
map.on('click', this.handleClick);
}
handleClick() {
console.log(this);
}
}
Внутри метода значение this может оказаться
неожиданным.
class MapController {
constructor(map) {
this.map = map;
this.handleClick =
this.handleClick.bind(this);
map.on('click', this.handleClick);
}
handleClick() {
console.log(this.map);
}
}
class MapController {
handleClick = () => {
console.log(this.map);
};
}
Стрелочная функция сохраняет внешний контекст.
Для обнаружения проблем полезно подписываться на ошибки карты.
map.on('error', event => {
console.error(event.error);
});
Через это событие можно выявить:
Игнорирование события error существенно усложняет
диагностику.
Эффективная схема диагностики включает несколько последовательных шагов:
error.idle вместо преждевременной обработки
данных.Систематическое применение этих приёмов позволяет быстро локализовать большинство проблем, связанных с событиями в MapLibre GL JS, даже в крупных интерактивных картографических приложениях.