Покадровая анимация в Mapbox GL JS опирается на непрерывное
обновление состояния карты с заданной частотой, обычно
синхронизированной с циклом рендеринга браузера через
requestAnimationFrame. Такой подход позволяет создавать
плавные визуальные эффекты: движение объектов, изменение геометрии,
динамическую стилизацию слоёв и симуляцию временных процессов.
В отличие от декларативных переходов (transition в
стилях слоёв), покадровая анимация реализуется на уровне программного
управления данными и стилем, где каждый кадр представляет собой
обновлённое состояние источников данных или параметров слоёв.
Покадровая анимация строится вокруг повторяющегося цикла:
Ключевой механизм — requestAnimationFrame,
обеспечивающий синхронизацию с частотой обновления экрана.
function animate() {
// вычисление нового состояния
updateData();
// повторный вызов на следующий кадр
requestAnimationFrame(animate);
}
animate();
В контексте Mapbox GL JS каждый вызов updateData()
обычно связан с изменением GeoJSON-источника или параметров
визуализации.
Наиболее распространённый способ покадровой анимации — обновление
GeoJSONSource.
map.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
});
Далее в цикле анимации происходит модификация данных:
function updateData(time) {
const source = map.getSource('points');
const features = [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
Math.sin(time / 1000) * 10,
Math.cos(time / 1000) * 10
]
}
}
];
source.setData({
type: 'FeatureCollection',
features
});
}
Метод setData полностью заменяет содержимое источника,
что делает его ключевым инструментом покадровой анимации.
Покадровая анимация почти всегда привязана ко времени. В качестве
базового источника используется performance.now():
let start = performance.now();
function animate() {
const time = performance.now() - start;
updateFrame(time);
requestAnimationFrame(animate);
}
animate();
Такой подход обеспечивает независимость анимации от реального времени загрузки страницы и позволяет точно контролировать скорость движения объектов.
Одним из типичных сценариев является перемещение объекта по линии или маршруту.
const route = [
[0, 0],
[5, 5],
[10, 0]
];
Интерполяция между точками:
function interpolate(a, b, t) {
return [
a[0] + (b[0] - a[0]) * t,
a[1] + (b[1] - a[1]) * t
];
}
Анимационный цикл:
let segment = 0;
let t = 0;
function animate() {
t += 0.01;
if (t > 1) {
t = 0;
segment = (segment + 1) % (route.length - 1);
}
const position = interpolate(route[segment], route[segment + 1], t);
map.getSource('moving-point').setData({
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: position
}
}]
});
requestAnimationFrame(animate);
}
animate();
Помимо обновления данных, возможно изменение визуальных параметров слоёв.
map.setPaintProperty('route-line', 'line-opacity', 0.5);
В покадровом режиме:
function animate(time) {
const opacity = (Math.sin(time / 500) + 1) / 2;
map.setPaintProperty('route-line', 'line-opacity', opacity);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Такой подход используется для эффектов пульсации, свечения, акцентирования объектов.
Сложные сцены часто требуют одновременного изменения геометрии и визуальных свойств.
function animate(time) {
const t = time / 1000;
const x = Math.cos(t) * 20;
const y = Math.sin(t) * 20;
const size = 5 + Math.sin(t * 2) * 2;
map.getSource('animated-point').setData({
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [x, y]
},
properties: {
size
}
}]
});
map.setPaintProperty('point-layer', 'circle-radius', ['get', 'size']);
requestAnimationFrame(animate);
}
Здесь используется связка:
Ключевым ограничением покадровой анимации в Mapbox GL JS является стоимость перерисовки карты.
Основные факторы нагрузки:
setDataДля оптимизации применяются следующие техники:
let last = 0;
function animate(time) {
if (time - last > 33) {
updateFrame(time);
last = time;
}
requestAnimationFrame(animate);
}
Обновляется только изменяемая часть состояния, без пересоздания всей коллекции объектов.
При работе с десятками и сотнями объектов прямое обновление GeoJSON становится узким местом.
Оптимизация достигается через:
FeatureCollection с фиксированной
структуройconst features = new Array(1000).fill(null).map(() => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [0, 0] }
}));
const data = {
type: 'FeatureCollection',
features
};
В цикле изменяются только координаты:
for (let i = 0; i < features.length; i++) {
features[i].geometry.coordinates[0] += Math.random() - 0.5;
features[i].geometry.coordinates[1] += Math.random() - 0.5;
}
map.getSource('many-points').setData(data);
Для устранения рывков применяется интерполяция значений между кадрами.
Базовая линейная интерполяция:
value = a + (b - a) * t;
Векторная форма для координат:
function lerpCoord(a, b, t) {
return [
a[0] + (b[0] - a[0]) * t,
a[1] + (b[1] - a[1]) * t
];
}
Сглаживание ускорений часто реализуется через easing-функции:
function easeInOut(t) {
return t * t * (3 - 2 * t);
}
Поскольку Mapbox GL JS работает в пространстве Web Mercator, любые анимации должны учитывать географические преобразования.
При перемещении объектов:
Symbol layers позволяют анимировать не только позицию, но и ориентацию объектов.
map.addLayer({
id: 'vehicles',
type: 'symbol',
source: 'vehicles',
layout: {
'icon-image': 'car',
'icon-rotate': ['get', 'heading']
}
});
Обновление направления:
feature.properties.heading = angle;
Хотя термин “покадровая” подразумевает дискретные обновления, фактически система работает как непрерывная функция времени:
state(t) → render
Каждый кадр представляет собой срез этой функции, а
requestAnimationFrame выступает синхронизатором между
вычислительной моделью и рендерингом WebGL.
Временные визуализации (например, движение транспорта или изменение погодных данных) реализуются через:
function getStateAt(time) {
const t0 = Math.floor(time);
const t1 = t0 + 1;
const f = time - t0;
return interpolateState(state[t0], state[t1], f);
}
Mapbox GL JS использует WebGL как основу рендеринга, поэтому каждая покадровая анимация фактически приводит к:
Это объясняет высокую стоимость частых обновлений и необходимость оптимизации.
При наличии нескольких независимых анимационных процессов используется единый таймер:
function animate(time) {
animatePoints(time);
animateLines(time);
animateStyles(time);
requestAnimationFrame(animate);
}
Централизация таймера позволяет избежать расхождения фаз движения объектов.
Распространённые архитектурные подходы:
const state = {
time: 0,
objects: []
};
function update(state, dt) {
state.time += dt;
return state;
}
function render(state) {
map.getSource('layer').setData(toGeoJSON(state));
}
setDataЭти ограничения определяют выбор между покадровой анимацией и декларативными средствами стилей в Mapbox GL JS