Покадровая анимация в Deck.gl основана на непрерывном обновлении состояния сцены с последующим пересчётом и перерисовкой WebGL-слоёв. Каждый кадр представляет собой снимок визуального состояния, зависящий от входных данных, временных параметров и текущего состояния камеры.
Ключевой особенностью архитектуры является декларативный подход: визуализация описывается через слои (layers), а изменение сцены достигается обновлением свойств, приводящим к повторному вычислению геометрии и шейдерных атрибутов.
Базовый механизм анимации строится вокруг
requestAnimationFrame, обеспечивающего синхронизацию с
частотой обновления экрана.
Каждый кадр включает последовательность операций:
Типовой цикл:
let t0 = performance.now();
function animate() {
const t = performance.now();
const time = (t - t0) / 1000;
deck.setProps({
layers: createLayers(time)
});
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Важным аспектом является минимизация перерасчётов: обновление слоёв должно происходить только при изменении значимых параметров.
Покадровая анимация часто опирается на явную временную переменную. В Deck.gl она не встроена как глобальная сущность, но используется как пользовательское состояние.
Распространённая модель — нормализованное время:
const speed = 0.01;
let time = 0;
function animate() {
time += speed;
deck.setProps({
layers: createLayers(time % 1)
});
requestAnimationFrame(animate);
}
Нормализация времени позволяет создавать циклические анимации без переполнения значений.
В более сложных сценариях используется синхронизация с реальными данными (например, GPS-треками или временными рядами), где время становится ключом выборки.
Одним из распространённых типов покадровой анимации является движение
камеры. В Deck.gl камера управляется объектом
viewState.
Изменение позиции, масштаба и угла наклона выполняется на каждом кадре:
function animate() {
const bearing = (performance.now() * 0.01) % 360;
deck.setProps({
viewState: {
longitude: 37.6,
latitude: 55.7,
zoom: 10,
bearing,
pitch: 45
}
});
requestAnimationFrame(animate);
}
Такой подход используется для вращения сцены, плавного зума или следования за объектом.
Deck.gl оптимизирует работу через систему атрибутов WebGL. Каждый
слой имеет набор accessor-функций
(getPosition, getColor,
getWidth), которые пересчитываются только при изменении
зависимостей.
Покадровая анимация часто требует принудительного обновления атрибутов:
new ScatterplotLayer({
id: 'points',
data,
getPosition: d => [d.x, d.y],
getRadius: d => d.size * Math.sin(time),
updateTriggers: {
getRadius: time
}
});
updateTriggers выступает механизмом контроля пересчёта:
изменение времени инициирует перерасчёт только конкретного атрибута, а
не всего слоя.
Для обеспечения плавности анимации используется линейная интерполяция между состояниями.
Формально:
x(t) = x_0 + (x_1 - x_0)t
где параметр t изменяется от 0 до 1 между кадрами.
Практическая реализация:
function lerp(a, b, t) {
return a + (b - a) * t;
}
function interpolatePosition(p0, p1, t) {
return [
lerp(p0[0], p1[0], t),
lerp(p0[1], p1[1], t)
];
}
Интерполяция применяется для координат, цветов, радиусов и параметров камеры.
PathLayer используется для отображения линий, где
анимация строится на постепенном «прорисовывании» пути.
Принцип заключается в использовании временного среза массива координат:
new PathLayer({
id: 'path',
data,
getPath: d => d.path,
getColor: [255, 0, 0],
widthMinPixels: 2,
getDashArray: [10, 5]
});
Для покадровой анимации путь ограничивается по индексу:
function getAnimatedPath(path, t) {
const len = Math.floor(path.length * t);
return path.slice(0, len);
}
Такой подход создаёт эффект «рисования» маршрута во времени.
TripsLayer специально предназначен для временной
анимации перемещений объектов. Он интерпретирует третью координату как
время.
new TripsLayer({
id: 'trips',
data,
getPath: d => d.path,
getTimestamps: d => d.timestamps,
currentTime: time,
trailLength: 200
});
Каждый кадр изменяет currentTime, создавая эффект
движения объектов вдоль траектории.
Ключевой особенностью является GPU-ускоренная интерполяция внутри шейдеров, что значительно снижает нагрузку на CPU.
При необходимости низкоуровневого контроля используется
Layer с переопределением жизненного цикла:
initializeStateupdateStatedrawПример структуры анимируемого слоя:
class AnimatedLayer extends DeckGL.Layer {
initializeState() {
const { gl } = this.context;
}
updateState({ props, oldProps }) {
if (props.time !== oldProps.time) {
this.setNeedsUpdate();
}
}
draw({ uniforms }) {
const { time } = this.props;
this.state.model.setUniforms({
u_time: time
});
this.state.model.draw();
}
}
Шейдеры GLSL становятся основным инструментом покадровой анимации:
uniform float u_time;
void main() {
float offset = sin(u_time + position.x);
gl_Position = project_position(vec3(position.xy, offset));
}
Покадровая анимация в WebGL чувствительна к стоимости пересчётов и перерисовки. Основные оптимизационные принципы:
updateTriggers вместо полного обновления
props;Пример ограничения частоты:
let last = 0;
function animate(t) {
if (t - last > 16) {
last = t;
deck.setProps({
layers: createLayers(t)
});
}
requestAnimationFrame(animate);
}
В сложных сценах несколько слоёв должны анимироваться синхронно. Для этого используется единый источник времени:
function renderFrame(time) {
const t = time * 0.001;
deck.setProps({
layers: [
layerA(t),
layerB(t),
layerC(t)
]
});
requestAnimationFrame(renderFrame);
}
Согласованность времени предотвращает десинхронизацию визуальных
эффектов, особенно при комбинировании TripsLayer,
ScatterplotLayer и ArcLayer.
Временные ряды позволяют строить покадровую визуализацию без искусственного генератора времени.
Пример структуры данных:
{
"time": [0, 1, 2, 3],
"value": [10, 15, 12, 20]
}
Отображение через фильтрацию:
function getFrame(data, t) {
return data.filter(d => d.time <= t);
}
Такой подход применяется для визуализации потоков, транспорта и изменений плотности данных.
При строгой необходимости воспроизводимости используется детерминированное обновление состояния:
const frame = 0;
function animate() {
const t = frame * 0.016;
frame++;
deck.setProps({
layers: createLayers(t)
});
requestAnimationFrame(animate);
}
Такой подход обеспечивает идентичность результатов между запусками рендеринга.