Автовоспроизведение

Общая модель анимации в Mapbox GL JS

В Mapbox GL JS анимация карты строится вокруг непрерывного изменения состояния map через функции обновления кадра. Основной механизм основан на цикле рендеринга, который синхронизирован с requestAnimationFrame. Это позволяет изменять параметры камеры, источников данных и слоёв с высокой частотой без ручного управления перерисовкой.

Ключевым элементом является метод:

map.triggerRepaint()

Он принудительно инициирует перерисовку кадра, если анимация управляется внешним таймером или асинхронной логикой.

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


Автоматическое движение камеры

Базовая анимация через easeTo

Наиболее распространённый способ реализации автовоспроизведения — циклическое изменение параметров камеры:

map.easeTo({
  center: [37.618423, 55.751244],
  zoom: 10,
  duration: 4000,
  essential: true
});

Метод easeTo использует плавную интерполяцию между текущим и целевым состоянием камеры. При последовательном вызове формируется эффект непрерывного движения.


Использование flyTo для сценариев перелёта

Метод flyTo создаёт более сложную траекторию движения камеры, включая имитацию полёта с ускорением и замедлением:

map.flyTo({
  center: [30.31413, 59.93863],
  zoom: 12,
  speed: 0.8,
  curve: 1.42
});

Автовоспроизведение на базе flyTo часто применяется для последовательного обхода точек интереса.


Организация последовательности точек

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

const points = [
  [37.62, 55.75],
  [30.31, 59.93],
  [82.92, 55.03]
];

let index = 0;

function playSequence() {
  map.flyTo({
    center: points[index],
    zoom: 10,
    speed: 0.9
  });

  index = (index + 1) % points.length;

  setTimeout(playSequence, 5000);
}

playSequence();

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


Автопроигрывание временных данных

Управление источниками данных во времени

Mapbox GL JS поддерживает обновление GeoJSON-источников в реальном времени. Это позволяет реализовать воспроизведение событий на карте.

map.getSource('points').setData(updatedGeojson);

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


Таймлайн через фильтрацию данных

Один из распространённых методов — фильтрация по временной метке:

map.setFilter('events-layer', [
  '<=',
  ['get', 'timestamp'],
  currentTime
]);

Изменяя currentTime в цикле, создаётся эффект проигрывания событий.


Пример временного цикла

let t = 0;

function animateTime() {
  t += 1000;

  map.setFilter('events-layer', [
    '<=',
    ['get', 'time'],
    t
  ]);

  map.triggerRepaint();
  requestAnimationFrame(animateTime);
}

animateTime();

Автовоспроизведение слоёв

Пошаговое включение и выключение слоёв

Слои можно использовать как кадры анимации, переключая их видимость:

map.setLayoutProperty('layer-1', 'visibility', 'visible');
map.setLayoutProperty('layer-2', 'visibility', 'none');

При последовательной смене создаётся эффект слайд-шоу.


Организация кадровой анимации

const layers = ['step-1', 'step-2', 'step-3'];
let frame = 0;

function playLayers() {
  layers.forEach((layer, i) => {
    map.setLayoutProperty(layer, 'visibility', i === frame ? 'visible' : 'none');
  });

  frame = (frame + 1) % layers.length;

  setTimeout(playLayers, 2000);
}

playLayers();

Управление кадрами через requestAnimationFrame

Низкоуровневое автовоспроизведение

Для более точного контроля используется requestAnimationFrame, который синхронизирует обновления с частотой обновления экрана:

function loop() {
  map.rotateTo((map.getBearing() + 0.5) % 360, { duration: 0 });

  requestAnimationFrame(loop);
}

loop();

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


Комбинирование с triggerRepaint

При изменении источников вне встроенных анимационных методов требуется принудительная перерисовка:

function update() {
  // изменение данных или состояния
  map.triggerRepaint();
  requestAnimationFrame(update);
}

update();

Автовоспроизведение маршрутов

Анимация движения объекта по линии

Маршруты реализуются через GeoJSON LineString и интерполяцию координат.

const route = {
  type: 'Feature',
  geometry: {
    type: 'LineString',
    coordinates: [
      [37.62, 55.75],
      [38.00, 56.00]
    ]
  }
};

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


Интерполяция позиции

function interpolate(a, b, t) {
  return [
    a[0] + (b[0] - a[0]) * t,
    a[1] + (b[1] - a[1]) * t
  ];
}

Циклическое движение маркера

let progress = 0;

function animateMarker() {
  const start = route.geometry.coordinates[0];
  const end = route.geometry.coordinates[1];

  const pos = interpolate(start, end, progress);

  map.getSource('point').setData({
    type: 'Feature',
    geometry: {
      type: 'Point',
      coordinates: pos
    }
  });

  progress += 0.01;

  if (progress > 1) progress = 0;

  requestAnimationFrame(animateMarker);
}

animateMarker();

Синхронизация нескольких анимаций

Камера + данные + слои

В сложных сценариях автовоспроизведение включает несколько параллельных процессов:

  • движение камеры
  • обновление источников данных
  • переключение слоёв
function tick() {
  map.easeTo({
    bearing: map.getBearing() + 0.2,
    duration: 0
  });

  updateDataFrame();
  updateLayersFrame();

  requestAnimationFrame(tick);
}

tick();

Контроль состояния цикла

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

let playing = true;

function loop() {
  if (!playing) return;

  map.rotateTo(map.getBearing() + 0.3, { duration: 0 });
  requestAnimationFrame(loop);
}

loop();

Оптимизация автовоспроизведения

Ограничение частоты обновлений

Чрезмерное обновление источников приводит к нагрузке на GPU. Используется троттлинг:

let last = 0;

function update(ts) {
  if (ts - last > 50) {
    map.triggerRepaint();
    last = ts;
  }

  requestAnimationFrame(update);
}

update();

Минимизация пересчёта слоёв

Слои с выражениями (expressions) следует проектировать так, чтобы они зависели от одного параметра, а не множества динамических значений, иначе каждая итерация вызывает перерасчёт всего стека рендеринга.


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

Запуск анимации при загрузке стиля

map.on('load', () => {
  startPlayback();
});

Привязка к завершению анимации камеры

map.on('moveend', () => {
  nextStep();
});

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


Переходы между сценами

Концепция сцен

Сцена представляет собой фиксированное состояние карты: центр, масштаб, наклон, набор видимых слоёв.

const scenes = [
  { center: [37, 55], zoom: 9 },
  { center: [30, 59], zoom: 10 }
];

Последовательное воспроизведение сцен

let s = 0;

function playScenes() {
  const scene = scenes[s];

  map.easeTo(scene);

  s = (s + 1) % scenes.length;

  setTimeout(playScenes, 6000);
}

playScenes();

Управление скоростью автовоспроизведения

Параметры скорости камеры

Для flyTo и easeTo ключевым параметром является speed или duration, влияющий на восприятие темпа воспроизведения.

map.flyTo({
  center: [10, 50],
  speed: 1.2
});

Динамическая скорость

let speed = 0.5;

function animate() {
  speed += 0.01;

  map.rotateTo(map.getBearing() + speed, { duration: 0 });

  requestAnimationFrame(animate);
}

animate();

Управление памятью и устойчивостью цикла

Очистка таймеров

clearTimeout(timerId);
cancelAnimationFrame(frameId);

Защита от утечек циклов

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


Координация автовоспроизведения с пользовательскими действиями

Приоритет взаимодействия

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

map.on('mousedown', () => playing = false);
map.on('mouseup', () => playing = true);

Возобновление цикла

function resume() {
  if (!playing) return;
  requestAnimationFrame(loop);
}