Анимация свойств слоев

В системе рендеринга MapLibre GL JS слой является декларативным описанием того, как конкретный тип географических данных должен отображаться на карте. Каждый слой содержит набор свойств стиля, которые делятся на две большие группы: layout-параметры (определяют структуру и размещение геометрии) и paint-параметры (отвечают за визуальное оформление).

Анимация в контексте слоёв MapLibre GL JS связана преимущественно с изменением paint-свойств во времени. Архитектура движка основана на WebGL и декларативном стиле описания, поэтому «анимация» не является встроенной сущностью уровня CSS keyframes, а реализуется через интерполяцию значений, выражения (expressions) и программное обновление стиля.


Модель слоёв и ограничение прямой анимации

Каждый слой в стиле описывается JSON-структурой:

  • id — идентификатор слоя
  • type — тип (fill, line, circle, symbol, heatmap и др.)
  • source — источник данных
  • paint — визуальные свойства
  • layout — структурные свойства

Прямое изменение свойств слоя мгновенно приводит к перерендеру. Это означает, что без дополнительных механизмов перехода любые изменения будут «резкими».

Ключевое ограничение: MapLibre GL JS не хранит историю состояний слоя, поэтому анимация всегда строится как промежуточная интерполяция между двумя значениями.


Принципы анимации paint-свойств

Анимация строится на трёх базовых подходах:

  1. Transition API (встроенные переходы)
  2. Expression-based интерполяция
  3. Императивная анимация через JavaScript (requestAnimationFrame)

Каждый подход имеет собственную область применения и ограничения.


Плавные переходы через transition

В стилях MapLibre GL JS можно задать глобальные параметры перехода:

map.setStyle({
  version: 8,
  sources: {...},
  layers: [...],
  transition: {
    duration: 1000,
    delay: 0
  }
});

Также переходы можно задавать точечно:

map.setPaintProperty('water-layer', 'fill-opacity-transition', {
  duration: 800,
  delay: 0
});

Или напрямую изменять значение:

map.setPaintProperty('water-layer', 'fill-opacity', 0.5);

Механика работы transition

При изменении свойства движок:

  • фиксирует начальное значение
  • вычисляет конечное значение
  • интерполирует значение по времени кадра
  • обновляет WebGL буферы

Интерполяция линейная по умолчанию, но может быть уточнена через expressions.


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

Наиболее мощный механизм анимации основан на выражении interpolate.

Пример изменения прозрачности в зависимости от зума:

'fill-opacity': [
  'interpolate',
  ['linear'],
  ['zoom'],
  5, 0.2,
  10, 0.8
]

Здесь значение автоматически изменяется при изменении zoom-уровня карты.


Анимация свойств через динамическое обновление стиля

Для более сложных сценариев используется программное изменение свойств.

Базовый цикл анимации

let opacity = 0;

function animate() {
  opacity += 0.01;

  map.setPaintProperty('building-layer', 'fill-opacity', opacity);

  if (opacity < 1) {
    requestAnimationFrame(animate);
  }
}

animate();

Этот подход даёт полный контроль над траекторией изменения.


Использование requestAnimationFrame как основа анимации

requestAnimationFrame синхронизируется с частотой обновления экрана. Это критично для MapLibre GL JS, так как WebGL-рендерер работает в том же цикле кадров.

Типовой шаблон:

const start = performance.now();
const duration = 2000;

function animate(time) {
  const t = Math.min((time - start) / duration, 1);

  const eased = t * t * (3 - 2 * t); // smoothstep

  map.setPaintProperty('roads', 'line-width', 1 + eased * 5);

  if (t < 1) {
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);

Анимация цвета и интерполяция RGB

Цветовые свойства в MapLibre GL JS поддерживают интерполяцию по компонентам.

Пример изменения цвета линии:

map.setPaintProperty('route', 'line-color', [
  'interpolate',
  ['linear'],
  ['zoom'],
  5, 'rgb(0, 0, 255)',
  10, 'rgb(255, 0, 0)'
]);

При этом движок разбивает цвет на каналы:

  • R (red)
  • G (green)
  • B (blue)
  • A (alpha)

Каждый канал интерполируется отдельно, что обеспечивает визуально гладкие переходы.


Анимация прозрачности (opacity)

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

  • fill-opacity
  • line-opacity
  • icon-opacity
  • text-opacity

Пример:

map.setPaintProperty('city-labels', 'text-opacity', [
  'interpolate',
  ['linear'],
  ['zoom'],
  8, 0,
  12, 1
]);

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


Анимация толщины линий

Свойство line-width часто используется для визуализации динамики маршрутов:

map.setPaintProperty('highways', 'line-width', [
  'interpolate',
  ['exponential', 1.5],
  ['zoom'],
  5, 1,
  14, 10
]);

Экспоненциальная интерполяция создаёт более «естественное» увеличение толщины.


Анимация кругов и точечных слоёв

Для circle слоёв часто применяется изменение радиуса:

map.setPaintProperty('points', 'circle-radius', [
  'interpolate',
  ['linear'],
  ['zoom'],
  5, 2,
  15, 20
]);

Также используется пульсация:

let radius = 5;
let direction = 1;

function pulse() {
  radius += direction * 0.2;

  if (radius > 15 || radius < 5) direction *= -1;

  map.setPaintProperty('pulse-layer', 'circle-radius', radius);

  requestAnimationFrame(pulse);
}

pulse();

Анимация heatmap слоёв

Heatmap-слои обладают специфическими свойствами:

  • heatmap-weight
  • heatmap-intensity
  • heatmap-radius

Пример динамического усиления:

map.setPaintProperty('heat', 'heatmap-intensity', [
  'interpolate',
  ['linear'],
  ['zoom'],
  5, 1,
  12, 3
]);

Изменение этих параметров создаёт эффект «разгорания» тепловой карты при приближении.


Использование filter для анимации видимости

Хотя filter не является визуальным свойством, он часто используется для анимации появления объектов.

let step = 0;

function animateFilter() {
  step += 0.02;

  map.setFilter('places', [
    '<=',
    ['get', 'importance'],
    step
  ]);

  if (step < 1) {
    requestAnimationFrame(animateFilter);
  }
}

animateFilter();

Таким образом происходит постепенное «проявление» объектов по атрибуту данных.


Комбинированные анимации нескольких свойств

Реальные сценарии часто требуют синхронного изменения нескольких параметров.

Пример анимации маршрута:

function animateRoute(t) {
  map.setPaintProperty('route', 'line-width', 2 + t * 6);
  map.setPaintProperty('route', 'line-opacity', t);
  map.setPaintProperty('route', 'line-blur', (1 - t) * 5);
}

Такая комбинация создаёт эффект «рисования линии».


Производительность и ограничения WebGL-пайплайна

Анимация в MapLibre GL JS работает в рамках WebGL pipeline, поэтому существуют важные ограничения:

  • каждое изменение paint-свойства может триггерить перерасчёт буферов
  • частые вызовы setPaintProperty могут вызывать лаги
  • сложные expressions увеличивают нагрузку на GPU
  • большое число анимируемых слоёв снижает FPS

Оптимизационные практики:

  • минимизация количества одновременно анимируемых свойств
  • предпочтение expressions вместо императивных циклов
  • использование zoom-based интерполяций вместо requestAnimationFrame там, где возможно
  • группировка слоёв

Expression-driven анимация как альтернатива циклам

Встроенные expressions позволяют переносить логику анимации в декларативную модель:

['interpolate',
 ['linear'],
 ['zoom'],
 0, 0,
 10, 1
]

Преимущества:

  • отсутствие JS-циклов
  • выполнение на уровне рендера
  • лучшая производительность
  • стабильная синхронизация с камерой

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

Многие анимации зависят от состояния камеры:

  • zoom
  • pitch
  • bearing

Пример:

map.setPaintProperty('buildings', 'fill-extrusion-height', [
  'interpolate',
  ['linear'],
  ['zoom'],
  15, 0,
  16, ['get', 'height']
]);

Это создаёт эффект «вырастания» зданий при приближении.


Анимация через временные атрибуты данных

Для сложных визуализаций используется обновление source данных:

map.getSource('points').setData({
  type: 'FeatureCollection',
  features: updatedFeatures
});

Если данные содержат временные параметры, можно реализовать анимацию движения объектов (например, транспорта).


Паттерны архитектуры анимации слоёв

В реальных приложениях применяются следующие паттерны:

  • State-based animation: управление через состояние приложения
  • Time-based animation: синхронизация с таймлайном
  • Data-driven animation: изменение геометрии
  • Camera-driven animation: реакция на движение карты

Каждый из них может комбинироваться с другими, образуя сложные визуальные системы.


Итоговая модель поведения анимации

Поведение анимации в MapLibre GL JS можно формализовать как:

  • вход: изменение состояния (значения свойств, камеры, данных)
  • процесс: интерполяция (CPU + GPU)
  • выход: WebGL frame rendering

Эта модель обеспечивает высокую производительность при условии корректного выбора механизма анимации и минимизации лишних перерисовок.