Изменения в событиях

Архитектура событий в MapLibre GL JS строится вокруг централизованной модели, где большинство объектов наследуют поведение от внутреннего класса Evented. Это обеспечивает единый механизм подписки, удаления подписок и одноразового срабатывания обработчиков.

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

  • события карты (map-level events);
  • события слоёв (layer interaction events);
  • события источников данных (source data events);
  • DOM-подобные события взаимодействия (pointer/mouse/touch);
  • системные события рендера и состояния.

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


Базовый API событий: on, off, once

Основные методы управления событиями остаются стабильным ядром API:

  • on(type, listener) — регистрация обработчика
  • off(type, listener) — удаление обработчика
  • once(type, listener) — одноразовая подписка

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

map.on('click', (e) => {
  console.log('click anywhere on map');
});

map.on('click', 'cities-layer', (e) => {
  console.log('click on feature in cities-layer');
});

Механизм делегирования событий слоям работает через внутренний hit-testing рендерера: при клике вычисляются пересечения с геометрией в текущем viewport.


Изменения в модели событий в современных версиях

Одним из ключевых направлений эволюции MapLibre GL JS стало перераспределение ответственности между UI-потоком и worker-потоками. Это напрямую повлияло на поведение событий.

Перенос вычислений в worker

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

  • геометрия и tile data обрабатываются в worker;
  • результаты hit-testing частично кэшируются;
  • события data и dataloading стали более асинхронными и предсказуемыми.

Это привело к изменению временных характеристик событий: порядок их срабатывания стал менее зависим от UI-потока.


Изменения в событиях загрузки и состояния карты

load, idle, render

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

  • load теперь строго означает завершение загрузки стиля и базовых ресурсов;
  • idle стал более строгим индикатором отсутствия активных задач рендера;
  • render может вызываться чаще из-за оптимизаций частичного обновления кадров.

В результате поведение idle стало менее «шумным» и более пригодным для триггеров автоматических действий (например, экспорт изображения карты или запуск аналитики состояния).


Изменения в событиях данных (data events)

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

data

Событие data теперь содержит расширенные поля:

  • dataType (source, style, tiles, glyphs и т.д.)
  • sourceId
  • tileId (при наличии)
  • isSourceLoaded

Это позволило точнее отслеживать, какой именно сегмент данных вызвал обновление.

dataloading

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


Изменения в pointer-событиях

Улучшение модели hit-testing

Одним из значимых изменений стала переработка определения попадания курсора в геометрические объекты.

Ранее hit-testing мог выполняться синхронно в UI-потоке, что приводило к лагам на сложных сценах. В новых версиях:

  • используется кэширование геометрии тайлов;
  • применяются предрасчитанные bounding volumes;
  • часть вычислений делегируется worker.

Это изменило поведение событий:

  • mousemove стал менее «тяжёлым»;
  • mouseenter и mouseleave стали более стабильными при высокой плотности объектов;
  • снизилось количество ложных срабатываний на границах полигонов.

Pointer events вместо mouse/touch разделения

MapLibre GL JS постепенно унифицирует взаимодействие через pointer-события:

  • pointerdown
  • pointerup
  • pointermove
  • pointerenter
  • pointerleave

При этом сохраняется совместимость с классическими mouse и touch событиями, но внутренняя реализация опирается на pointer abstraction layer.

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


Изменения в событиях рендера

render и postrender

Событие render теперь теснее связано с внутренним циклом WebGL:

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

postrender (если используется в кастомных сборках или расширениях) стал важен для синхронизации внешних WebGL-слоёв.


Проблема частоты событий

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

  • ранее использовался для простого трекинга состояния;
  • теперь требует throttling или debounce при внешней логике.

Изменения в событиях стиля

styledata

Событие styledata стало более детализированным:

  • разделено обновление слоёв и источников;
  • добавлена информация о типе изменения (add, remove, update);
  • улучшена согласованность с асинхронной загрузкой sprite и glyphs.

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


styledataloading

Введение промежуточных состояний загрузки стиля изменило поведение цепочек событий:

  • теперь можно отследить момент начала пересборки стиля;
  • улучшена синхронизация с UI-индикаторами загрузки;
  • уменьшено количество «рывков» при смене темы карты.

Изменения в обработке ошибок

error

Событие error стало более структурированным:

  • добавлены коды ошибок;
  • уточнены источники (network, tile, style, sprite, glyphs);
  • расширена трассировка контекста.

Это позволило унифицировать обработку ошибок на уровне приложения:

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

Изменения в порядке доставки событий

Асинхронность и event queue

Внутренний event loop был переработан:

  • события группируются в микропакеты;
  • часть событий откладывается до конца кадра;
  • устранены некоторые race conditions между render и data.

Это привело к следующим последствиям:

  • уменьшилось количество «дребезга» событий;
  • улучшилась предсказуемость последовательности data → render → idle;
  • некоторые события больше не гарантируют синхронное выполнение.

Влияние requestAnimationFrame

Многие события теперь привязаны к циклу requestAnimationFrame, что влияет на:

  • move и zoom события;
  • render цикл;
  • события изменения камеры.

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


Изменения в событиях камеры

move, moveend, movestart

События камеры стали более «агрегированными»:

  • movestart вызывается реже, с более строгими условиями начала движения;
  • move может быть пропущен при минимальных изменениях;
  • moveend теперь зависит от стабилизации рендера, а не только от завершения жеста.

zoom и rotate

События масштабирования и вращения получили более точную синхронизацию:

  • zoom теперь чаще сопровождается render;
  • rotate учитывает инерционные анимации;
  • улучшено разделение пользовательского ввода и программных изменений.

Изменения в пользовательских событиях (custom events)

MapLibre GL JS сохранил возможность генерации пользовательских событий через fire, однако поведение изменилось:

  • пользовательские события не всегда синхронны;
  • при высокой нагрузке могут быть отложены;
  • рекомендуется избегать использования как критического механизма синхронизации.
map.fire('custom-event', { payload: { id: 1 } });

В новых версиях такие события интегрируются в общий event queue, что повышает стабильность, но снижает предсказуемость точного момента вызова.


Изменения в отмене событий и утечках памяти

off и очистка подписок

Механизм off стал более строгим:

  • требуется точное совпадение ссылки на обработчик;
  • улучшена очистка внутренних ссылок;
  • уменьшен риск memory leak при динамическом добавлении слоёв.

Особенно важно при частом создании/удалении карт:

  • утечки через pointer-events были значительно снижены;
  • внутренние слушатели DOM теперь автоматически отсоединяются при remove().

Изменения в семантике событий векторных слоёв

При работе с vector tiles изменилось поведение событий клика:

  • попадание в feature теперь учитывает актуальный zoom level;
  • кеширование снижает вариативность результатов между событиями;
  • улучшена согласованность между click, hover и mousemove.

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


Изменения в производительности событий

Оптимизации событийной системы напрямую связаны с производительностью WebGL-рендеринга:

  • снижено количество синхронных пересчётов hit-test;
  • уменьшено количество JS-to-WebGL переходов;
  • улучшено батчирование событий в плотных сценах.

Следствием стало:

  • уменьшение CPU load при интерактивных картах;
  • более стабильный FPS при множественных обработчиках событий;
  • снижение jitter при drag/zoom взаимодействиях.