В основе анимационной модели Deck.gl лежит принцип реактивного
обновления состояния: визуализация пересчитывается при изменении входных
данных — viewState, свойств слоёв или глобального времени
анимации. Ключевой особенностью является разделение ответственности
между:
viewState)layers)Каждое изменение приводит к повторному вызову рендера через
внутренний цикл Deck или компонент
<DeckGL />, что позволяет строить как дискретные, так
и непрерывные анимации.
Синхронизация анимаций в сложных сценах требует введения общего временного контекста. Без него различные слои начинают «жить своей жизнью», что приводит к рассинхронизации визуальных эффектов.
На практике используется один из подходов:
performance.now()deltaTime в цикле рендераКлючевая идея — все анимационные вычисления должны зависеть от одного источника:
const t = performance.now();
или нормализованного времени:
const time = (performance.now() - startTime) / 1000;
Основной механизм синхронизации кадров —
requestAnimationFrame. Deck.gl предоставляет встроенную
поддержку анимационного цикла через параметр
animationLoop.
В низкоуровневом варианте цикл выглядит следующим образом:
function loop() {
const now = performance.now();
deck.setProps({
time: now
});
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
Внутри Deck.gl этот подход используется для триггера повторного рендера без необходимости ручного управления сценой.
Важно учитывать:
requestAnimationFrame синхронизирован с refresh rate
дисплеяОдна из самых частых задач — синхронизация анимации камеры с другими визуальными эффектами.
Deck.gl использует систему переходов (transitions), основанную на интерполяторах:
Пример синхронизации движения камеры:
import {FlyToInterpolator} from '@deck.gl/core';
const viewState = {
longitude: 30,
latitude: 50,
zoom: 10,
transitionDuration: 2000,
transitionInterpolator: new FlyToInterpolator()
};
Для синхронизации с другими анимациями важно учитывать:
Распространённая практика — запуск всех анимаций в одном кадре:
const t0 = performance.now();
deck.setProps({
viewState: {
...viewState,
transitionDuration: 2000
},
animationStart: t0
});
Сложные сцены включают десятки слоёв: линии, точки, extruded polygons, heatmap и кастомные WebGL-слои. Без централизованного времени каждый слой может обновляться независимо.
Подход к синхронизации:
timegetColor, getPosition,
updateTriggersПример:
const layers = [
new ScatterplotLayer({
id: 'points',
data,
getPosition: d => d.position,
getRadius: d => 100 * Math.sin(time * 0.001 + d.phase)
}),
new LineLayer({
id: 'lines',
data,
getSourcePosition: d => d.source,
getTargetPosition: d => d.target,
getWidth: d => 2 + Math.cos(time * 0.001)
})
];
Ключевой момент — одинаковая шкала времени обеспечивает фазовую согласованность эффектов.
Максимальная производительность достигается, когда анимация переносится в GPU через шейдеры. В этом случае синхронизация становится задачей передачи единого uniform-параметра.
Deck.gl автоматически прокидывает time в кастомные
шейдеры через uniforms:
const layer = new ScatterplotLayer({
id: 'animated-points',
data,
getPosition: d => d.position,
fp64: false,
// кастомный шейдерный параметр
getShaders: () => ({
inject: {
'vs:#decl': 'uniform float time;',
'vs:#main-end': `
gl_PointSize *= (1.0 + sin(time * 0.001) * 0.5);
`
}
}),
updateTriggers: {
time
}
});
Синхронизация достигается тем, что все GPU-эффекты получают один и
тот же uniform time.
Для сложных визуализаций вводится централизованный контроллер времени:
class TimeController {
constructor() {
this.start = performance.now();
this.speed = 1.0;
}
now() {
return (performance.now() - this.start) * this.speed;
}
setSpeed(s) {
this.speed = s;
}
}
const clock = new TimeController();
Использование:
function animate() {
const time = clock.now();
deck.setProps({
time,
layers: createLayers(time)
});
requestAnimationFrame(animate);
}
animate();
Такой подход обеспечивает:
Несогласованность анимаций часто возникает из-за различий в функциях сглаживания. Deck.gl использует easing как отдельный слой логики над интерполяцией.
Типичные функции:
При синхронизации важно использовать одинаковый easing для всех компонентов сцены:
transitionInterpolator: new FlyToInterpolator(),
transitionEasing: t => 1 - Math.pow(1 - t, 3)
Если камера использует cubic easing, а слои — linear, визуальная сцена распадается на несвязанные элементы движения.
На практике рассинхронизация возникает не из-за Deck.gl как такового, а из-за архитектуры приложения.
Основные источники проблем:
Date.now() и
performance.now())requestAnimationFrame циклыОсобенно критична ситуация, когда:
В результате сцена распадается на несогласованные движения.
Наиболее устойчивая архитектура строится вокруг единого источника состояния:
Схематически:
requestAnimationFrame
↓
global time
↓
create layers
↓
deck.setProps
↓
render
Такая модель устраняет дрейф времени между слоями и обеспечивает согласованное поведение всей сцены вне зависимости от сложности визуализации.