Render события

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

В отличие от событий высокого уровня (moveend, zoomend), render относится к низкоуровневому циклу визуализации и тесно связан с WebGL-конвейером.

map.on('render', () => {
    console.log('Карта перерисована');
});

Жизненный цикл кадра

Процесс отрисовки в MapLibre GL JS можно условно разделить на несколько этапов:

  1. Изменение состояния карты

    • перемещение камеры
    • масштабирование
    • вращение
    • изменение данных источников
  2. Планирование перерисовки Внутренний механизм вызывает map.triggerRepaint().

  3. Композиция сцены

    • вычисление тайлов
    • обновление буферов WebGL
    • применение стилей
  4. Отрисовка кадра

    • генерация WebGL draw calls
    • вывод изображения на canvas
  5. Событие render Срабатывает после завершения очередного кадра.

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


Отличие render от idle

Состояние «покоя» карты фиксируется через событие idle.

render

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

idle

  • вызывается, когда карта полностью стабилизировалась
  • нет ожидающих тайлов
  • нет запланированных перерисовок
  • отсутствует активная анимация
map.on('idle', () => {
    console.log('Карта находится в стабильном состоянии');
});

Разница принципиальна: render отражает процесс, idle — завершённое состояние.


Использование render для анимаций

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

map.on('render', () => {
    const center = map.getCenter();
    const zoom = map.getZoom();

    updateCustomOverlay(center, zoom);
});

Такой подход позволяет синхронизировать кастомные WebGL-слои, DOM-оверлеи или Canvas-элементы с текущим состоянием камеры.


Производственные ограничения

Высокая частота вызовов накладывает ограничения:

  • логирование внутри render приводит к деградации производительности
  • тяжёлые вычисления блокируют UI-поток
  • манипуляции DOM внутри обработчика вызывают перерасход ресурсов

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

let lastZoom;

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

    if (zoom !== lastZoom) {
        lastZoom = zoom;
        updateZoomDependentLayers(zoom);
    }
});

Связь с triggerRepaint

Метод map.triggerRepaint() напрямую влияет на генерацию событий render. Он принудительно помечает карту как требующую обновления.

map.triggerRepaint();

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

  • при кастомных источниках данных
  • при анимациях вне стандартного цикла
  • при интеграции с внешними таймерами

Без вызова triggerRepaint карта может перейти в состояние idle, даже если внешние данные изменились.


Координация с источниками данных

При загрузке тайлов и GeoJSON данных render тесно связан с событиями:

  • data
  • sourcedata
  • dataloading

Каждое обновление источника может инициировать новый цикл рендера.

map.on('sourcedata', (e) => {
    if (e.isSourceLoaded) {
        console.log('Источник загружен');
    }
});

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

Типичный порядок внутри одного цикла отрисовки:

  1. dataloading
  2. data
  3. обновление тайлов
  4. композиция стилей
  5. WebGL render pass
  6. render

При завершении всех активных задач дополнительно может быть вызван idle.


Использование render для синхронизации UI

render применяется для синхронизации внешних интерфейсных элементов с состоянием карты:

  • компасы и индикаторы направления
  • кастомные контролы масштаба
  • динамические подписи объектов
map.on('render', () => {
    const bearing = map.getBearing();
    compassElement.style.transform = `rotate(${bearing}deg)`;
});

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


Поток событий при анимации камеры

При активных изменениях камеры (flyTo, easeTo, jumpTo) последовательность событий становится непрерывной:

  • начинается серия render
  • изменяются параметры камеры
  • обновляются тайлы при необходимости
  • завершение анимации приводит к idle
map.flyTo({
    center: [30, 50],
    zoom: 10,
    duration: 2000
});

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


Типичные ошибки при работе с render

  • накопление обработчиков без очистки
  • выполнение тяжёлых расчётов без throttling
  • изменение состояния карты внутри render, вызывающее рекурсию
  • попытка использовать render как замену бизнес-логике
// проблемный паттерн
map.on('render', () => {
    map.setZoom(map.getZoom() + 0.01);
});

Подобный код приводит к неконтролируемому циклу перерисовки.


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

render часто используется как точка профилирования:

  • подсчёт FPS через интервалы
  • измерение времени кадра
  • контроль перегрузки WebGL
let lastTime = performance.now();
let frames = 0;

map.on('render', () => {
    frames++;
    const now = performance.now();

    if (now - lastTime >= 1000) {
        console.log('FPS:', frames);
        frames = 0;
        lastTime = now;
    }
});

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


Взаимодействие с пользовательскими слоями

При создании кастомных слоёв через CustomLayerInterface событие render часто используется для синхронизации WebGL-контекста.

  • подготовка матриц
  • обновление буферов
  • привязка к камере
const customLayer = {
    onRender(gl, matrix) {
        drawSomething(gl, matrix);
    }
};

Хотя здесь используется собственный API слоя, он концептуально совпадает с циклом render.