Альтернативные подходы для псевдо-3D

Lottie Web работает с JSON-анимациями, экспортированными из After Effects через Bodymovin, и по своей природе является 2D-движком. Любая «глубина» в таких анимациях создаётся не геометрией, а имитацией пространственных эффектов через трансформации слоёв, масштабирование, размытие и управление порядком отрисовки.

Ключевой принцип псевдо-3D в Lottie — разложение сцены на независимые визуальные плоскости и управление их поведением во времени или в зависимости от внешних параметров (скролл, курсор, таймлайн).

Основные инструменты:

  • transform.position
  • transform.scale
  • transform.rotation
  • opacity
  • порядок слоёв (stacking context)
  • маски и track mattes
  • дублирование композиции

Многослойная глубина через параллакс

Наиболее распространённый способ имитации 3D — параллакс-движение. Слои разделяются по «глубине», и каждый слой движется с разной скоростью относительно базового смещения.

В Lottie Web это достигается через программное управление прогрессом анимации или внешними трансформациями контейнеров.

Пример внешнего управления слоями:

const animation = lottie.loadAnimation({
  container: document.getElementById('scene'),
  renderer: 'svg',
  loop: true,
  autoplay: true,
  path: 'animation.json'
});

window.addEventListener('mousemove', (e) => {
  const x = (e.clientX / window.innerWidth - 0.5) * 2;

  document.querySelectorAll('.layer').forEach((layer, index) => {
    const depth = (index + 1) * 10;
    layer.style.transform = `translateX(${x * depth}px)`;
  });
});

Слои, экспортированные из Lottie, оборачиваются в дополнительные контейнеры, что позволяет добавлять псевдопространственное смещение без изменения JSON.


Имитация перспективы через масштабирование

Перспектива в 2D достигается через изменение масштаба объектов в зависимости от их «удалённости».

Чем дальше слой, тем меньше масштаб и ниже контраст.

function applyDepthScale(elements) {
  elements.forEach((el, i) => {
    const depth = i / elements.length;
    const scale = 1 - depth * 0.4;

    el.style.transform = `scale(${scale})`;
    el.style.opacity = `${1 - depth * 0.6}`;
  });
}

В Lottie JSON это может быть реализовано через keyframes масштаба, но более гибкий вариант — постобработка DOM-слоёв при renderer: 'svg'.


Управление глубиной через порядок слоёв

SVG-рендерер Lottie строго следует порядку слоёв, заданному в композиции After Effects. Однако визуальная глубина может быть изменена динамически:

  • пересортировка DOM-элементов
  • изменение z-index (при HTML-обёртке)
  • условное скрытие/появление элементов
function reorderByDepth(container) {
  const layers = Array.from(container.children);

  layers.sort((a, b) => {
    return parseFloat(a.dataset.depth) - parseFloat(b.dataset.depth);
  });

  layers.forEach(layer => container.appendChild(layer));
}

Такой подход позволяет создавать эффект «камера движется сквозь сцену».


Использование размытия для усиления глубины

Размытие активно используется как визуальный маркер расстояния. В псевдо-3D сценах ближние объекты резкие, дальние — размытые.

function applyDepthBlur(elements) {
  elements.forEach((el, i) => {
    const depth = i / elements.length;
    const blur = depth * 6;

    el.style.filter = `blur(${blur}px)`;
  });
}

В Lottie SVG-фильтры ограничены производительностью, поэтому предпочтительнее использовать CSS-фильтры на контейнере.


Камера как абстракция трансформаций

В Lottie нет камеры, но её поведение можно симулировать через глобальные трансформации сцены.

Основная идея — все слои помещаются в общий контейнер, который управляется как «камера»:

const camera = document.getElementById('camera');

function moveCamera(x, y, zoom) {
  camera.style.transform = `
    translate(${-x}px, ${-y}px)
    scale(${zoom})
  `;
}

Дополнительная логика:

  • инерция движения
  • сглаживание через requestAnimationFrame
  • ограничение зоны перемещения

Связка Lottie и пользовательского ввода

Интерактивность усиливает иллюзию глубины. Чаще всего используются:

  • движение мыши
  • скролл страницы
  • ориентация устройства
  • таймерные триггеры

Пример управления прогрессом анимации через скролл:

window.addEventListener('scroll', () => {
  const scrollTop = window.scrollY;
  const maxScroll = document.body.scrollHeight - window.innerHeight;

  const progress = scrollTop / maxScroll;

  animation.goToAndStop(progress * animation.totalFrames, true);
});

Таким образом сцена начинает вести себя как «камера в пространстве времени».


Псевдо-3D через дублирование композиции

Сильный эффект глубины достигается через многократное использование одной и той же Lottie-композиции с разными параметрами трансформации.

<div class="scene">
  <div class="layer back"></div>
  <div class="layer mid"></div>
  <div class="layer front"></div>
</div>
[lottie.loadAnimation({...}), lottie.loadAnimation({...}), lottie.loadAnimation({...})]

Каждый экземпляр:

  • имеет разный scale
  • различную прозрачность
  • разную скорость проигрывания

Это создаёт эффект объёмной сцены без 3D-движка.


Гибридный подход с WebGL

Для более сложных сцен Lottie используется как текстурный слой внутри WebGL-контекста.

Суть подхода:

  • Lottie рендерится в canvas
  • canvas используется как текстура
  • WebGL управляет камерой и перспективой

Пример интеграции с Three.js:

const texture = new THREE.CanvasTexture(lottieCanvas);

const material = new THREE.MeshBasicMaterial({
  map: texture,
  transparent: true
});

const plane = new THREE.PlaneGeometry(5, 5);
const mesh = new THREE.Mesh(plane, material);

scene.add(mesh);

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


Симуляция глубины через скорость анимации

Ещё один способ усиления псевдо-3D — изменение скорости воспроизведения в зависимости от «глубины слоя».

function setLayerSpeed(layer, depth) {
  const speed = 1 - depth * 0.7;
  layer.setSpeed(speed);
}

Ближние объекты «живут» быстрее, дальние — медленнее, что совпадает с естественным восприятием перспективы.


Ограничения SVG-рендеринга при псевдо-3D

SVG-рендерер Lottie имеет особенности, влияющие на реализацию глубины:

  • отсутствие реального z-buffer
  • дорогие фильтры при большом количестве слоёв
  • ограниченная производительность при частых transform-обновлениях
  • сложность синхронизации большого числа анимационных групп

Для сцен с высокой плотностью слоёв предпочтительнее renderer: 'canvas', где управление пикселями даёт больше свободы для постобработки глубины.


Комбинация масок и глубины

Маски используются для имитации перспективного перекрытия объектов.

layer.style.maskImage = 'linear-gradient(to top, transparent, black)';

Также применяется техника «вырезания» слоёв при пересечении виртуальной плоскости камеры, создавая эффект прохождения объектов через пространство.


Инерционные эффекты как замена физической камеры

Реалистичность псевдо-3D усиливается инерцией движения:

let targetX = 0;
let currentX = 0;

function animate() {
  currentX += (targetX - currentX) * 0.08;
  camera.style.transform = `translateX(${currentX}px)`;
  requestAnimationFrame(animate);
}

animate();

Такая модель создаёт ощущение массы у сцены, несмотря на её 2D-природу.


Композиционные паттерны глубины

На практике используются повторяемые структуры:

  • фоновые слои с минимальной анимацией
  • средний слой с локальными движениями
  • передний слой с интерактивными элементами
  • отдельный слой частиц с независимым таймингом

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


Эффект объёма через свет и контраст

Даже без настоящего 3D освещение можно симулировать:

  • затемнение дальних слоёв
  • усиление контраста на переднем плане
  • цветовой сдвиг в зависимости от глубины
el.style.filter = `brightness(${1 - depth * 0.5}) contrast(${1 + depth * 0.3})`;

Так формируется визуальная иерархия пространства.


Синхронизация нескольких Lottie-слоёв

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

const animations = layers.map(layer => lottie.loadAnimation({
  container: layer,
  renderer: 'svg',
  loop: false,
  autoplay: false,
  path: 'scene.json'
}));

function sync(progress) {
  animations.forEach(anim => {
    anim.goToAndStop(progress * anim.totalFrames, true);
  });
}

Это позволяет добиться согласованного движения «объёмной» сцены, собранной из нескольких 2D-компонентов.