Замеры времени рендеринга

Метрики времени рендеринга в Mapbox GL JS опираются на несколько уровней: жизненный цикл карты, события библиотеки, браузерные API измерения производительности и низкоуровневые особенности WebGL. Корректная оценка времени рендеринга требует разделения понятий «первый визуальный кадр», «полная загрузка стиля», «стабилизация слоя данных» и «устойчивый FPS в интерактиве».

При создании экземпляра карты Mapbox GL JS проходит последовательность состояний, каждое из которых можно использовать как точку отсчёта:

  • инициализация WebGL-контекста
  • загрузка стиля (style JSON)
  • загрузка источников данных (sources)
  • подготовка тайлов и ресурсов
  • первый рендер кадра
  • непрерывный рендер при интерактивных изменениях

С точки зрения измерений важно различать:

Первый визуальный рендер (first paint карты) — момент, когда карта впервые становится видимой. Полная загрузка (fully loaded) — когда все ресурсы стиля и источников загружены. Состояние idle — когда нет активных загрузок и перерисовок.

События Mapbox GL JS для таймингов

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

load

Срабатывает, когда стиль загружен и карта готова к дальнейшей работе.

render

Вызывается при каждом кадре рендеринга. Используется для измерения частоты и длительности отрисовки.

idle

Ключевое событие для определения завершения всех асинхронных операций:

  • завершены загрузки тайлов
  • нет ожидающих запросов
  • нет активного рендеринга

data

Срабатывает при загрузке или изменении данных.

Эти события формируют базовую шкалу измерений:

const start = performance.now();

map.on('load', () => {
  const loadTime = performance.now() - start;
  console.log('Load time:', loadTime);
});

Использование Performance API

Для точных измерений предпочтительно использовать performance.mark и performance.measure, так как они позволяют структурировать таймлайны.

performance.mark('map-start');

map.on('load', () => {
  performance.mark('map-load');
  performance.measure('map-load-time', 'map-start', 'map-load');

  const measure = performance.getEntriesByName('map-load-time')[0];
  console.log(measure.duration);
});

Преимущество такого подхода заключается в интеграции с DevTools Performance Timeline, где можно визуально анализировать узкие места.

Замер первого кадра

Первый кадр — критическая метрика для UX, особенно при тяжёлых стилях.

Mapbox GL JS не предоставляет прямого события «first render complete», поэтому используется комбинация render и requestAnimationFrame.

let firstFrame = false;
const start = performance.now();

map.on('render', () => {
  if (!firstFrame) {
    firstFrame = true;
    requestAnimationFrame(() => {
      const firstPaintTime = performance.now() - start;
      console.log('First render:', firstPaintTime);
    });
  }
});

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

Измерение времени до состояния idle

Состояние idle часто используется как более строгий критерий завершённой загрузки, чем load.

const start = performance.now();

map.on('idle', () => {
  const idleTime = performance.now() - start;
  console.log('Map fully idle:', idleTime);
});

Важно учитывать, что idle может срабатывать неоднократно при динамическом изменении источников.

Для однократного измерения используют флаг:

let done = false;

map.on('idle', () => {
  if (done) return;
  done = true;

  const t = performance.now() - start;
  console.log('Final idle:', t);
});

FPS и измерение кадрового времени

Для оценки плавности интерактивного рендеринга используется requestAnimationFrame.

let last = performance.now();
let frames = 0;
let fpsSum = 0;

function loop() {
  const now = performance.now();
  const delta = now - last;
  last = now;

  const fps = 1000 / delta;
  fpsSum += fps;
  frames++;

  requestAnimationFrame(loop);
}

loop();

setTimeout(() => {
  console.log('Average FPS:', fpsSum / frames);
}, 5000);

Такой метод позволяет оценить:

  • средний FPS
  • вариативность кадров
  • просадки при загрузке тайлов

WebGL контекст и GPU-ограничения

Mapbox GL JS использует WebGL, поэтому значительная часть времени уходит в GPU-пайплайн. Это создаёт ограничения:

  • JavaScript не видит реальное время выполнения шейдеров
  • performance.now() фиксирует только CPU-стадию
  • GPU может отставать на несколько кадров

Для более точной диагностики можно использовать расширения:

  • EXT_disjoint_timer_query_webgl2 (если доступно)
  • WEBGL_debug_renderer_info (для диагностики устройства)

Однако поддержка ограничена, поэтому чаще применяют косвенные метрики.

Разделение времени рендеринга на стадии

Для аналитики полезно разбивать рендеринг на этапы:

Инициализация карты

  • создание WebGL context
  • загрузка стиля

Загрузка ресурсов

  • тайлы векторных/растровых источников
  • шрифты и спрайты

Построение сцены

  • генерация буферов
  • подготовка слоёв

GPU рендер

  • отрисовка кадра

Каждый этап можно частично отследить через события:

map.on('data', (e) => {
  console.log('Data event:', e.type);
});

Практическая схема комплексного измерения

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

const metrics = {
  start: performance.now(),
  load: null,
  firstRender: null,
  idle: null
};

map.on('load', () => {
  metrics.load = performance.now();
});

map.on('render', () => {
  if (!metrics.firstRender) {
    metrics.firstRender = performance.now();
  }
});

map.on('idle', () => {
  if (!metrics.idle) {
    metrics.idle = performance.now();

    console.log({
      load: metrics.load - metrics.start,
      firstRender: metrics.firstRender - metrics.start,
      idle: metrics.idle - metrics.start
    });
  }
});

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

  • влияние разных стилей
  • количество слоёв
  • сложность источников данных
  • производительность устройств

Синхронизация с DevTools Performance

При анализе в Chrome DevTools полезно совмещать ручные метки с профилированием:

  • запись Performance trace
  • анализ Main thread
  • GPU activity (через layers / rendering tab)

Mapbox GL JS хорошо отображается в профилировщике как серия WebGL draw calls и script execution blocks, что позволяет сопоставить измерения из кода с визуальным таймлайном браузера.

Особенности измерений при анимации и интерактиве

При использовании flyTo, easeTo и других анимационных методов метрики становятся непрерывными:

map.flyTo({
  center: [10, 20],
  zoom: 5
});

В таких сценариях:

  • render вызывается на каждом кадре анимации
  • idle наступает только после завершения перехода
  • FPS становится основным индикатором плавности

Для оценки стабильности используют скользящее окно FPS и время между кадрами, а не одиночные измерения.

Ограничения точности измерений

При работе с Mapbox GL JS важно учитывать системные ограничения:

  • event loop браузера влияет на точность performance.now()
  • background tabs могут замедлять рендер
  • GPU scheduling скрыт от JavaScript
  • разные устройства дают несопоставимые результаты

Поэтому корректные замеры всегда требуют:

  • повторяемости тестов
  • фиксированного набора данных
  • одинаковых zoom/center параметров
  • отключения лишних слоёв и эффектов