Создание анимаций вручную

В библиотеке Three.js анимация объектов в сцене строится вокруг объекта THREE.Clock и функции рендеринга. Основная идея заключается в том, чтобы на каждом кадре вычислять текущее состояние сцены и обновлять свойства объектов: позиции, вращение, масштаб, материалы и свет. Анимация строится как постепенное изменение этих свойств во времени.

const clock = new THREE.Clock();

function animate() {
    requestAnimationFrame(animate);
    
    const delta = clock.getDelta(); // время с последнего кадра
    cube.rotation.x += delta;       // вращение куба
    cube.rotation.y += delta;
    
    renderer.render(scene, camera);
}

animate();

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


Работа с временными функциями (easing)

Простое линейное изменение свойств выглядит неестественно. Для более реалистичных анимаций применяются временные функции. Например, можно использовать синусоиду, экспоненту или кубическую функцию для сглаживания движения:

const time = clock.getElapsedTime();
cube.position.y = Math.sin(time) * 2; // плавное вертикальное колебание

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

function easeInOutQuad(t) {
    return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}

const duration = 2; // секунды
let elapsed = (clock.getElapsedTime() % duration) / duration;
cube.position.x = easeInOutQuad(elapsed) * 5;

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


Анимация нескольких объектов

Для управления несколькими объектами удобно хранить их состояния в массиве или объекте. Применение итерации по коллекции позволяет обновлять свойства сразу нескольких элементов:

const spheres = [];
for (let i = 0; i < 10; i++) {
    const sphere = new THREE.Mesh(
        new THREE.SphereGeometry(0.5, 32, 32),
        new THREE.MeshStandardMaterial({ color: 0xff0000 })
    );
    sphere.position.x = i * 2;
    scene.add(sphere);
    spheres.push(sphere);
}

function animate() {
    requestAnimationFrame(animate);
    const time = clock.getElapsedTime();

    spheres.forEach((s, i) => {
        s.position.y = Math.sin(time + i) * 2;
        s.rotation.y += 0.01 * (i + 1);
    });

    renderer.render(scene, camera);
}

animate();

Использование отдельного смещения времени (time + i) создаёт эффект каскадной анимации, когда объекты движутся с разной фазой.


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

Three.js позволяет изменять не только геометрию и трансформации, но и свойства материалов. Например, изменение цвета, прозрачности, текстур или интенсивности света:

const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
cube.material = material;

function animate() {
    requestAnimationFrame(animate);
    const time = clock.getElapsedTime();

    material.color.r = (Math.sin(time) + 1) / 2; // плавное изменение красного канала
    material.color.g = (Math.cos(time) + 1) / 2;

    renderer.render(scene, camera);
}

animate();

Такой подход открывает возможности для динамического освещения и визуальных эффектов, например, мерцания или смены цветовой палитры.


Комбинированные анимации

Для сложных сцен объекты могут одновременно изменять несколько параметров: положение, вращение, масштаб, цвет, прозрачность. Важно синхронизировать эти изменения через одну функцию анимации:

function animate() {
    requestAnimationFrame(animate);
    const time = clock.getElapsedTime();

    cube.position.x = Math.sin(time) * 3;
    cube.rotation.y = time;
    cube.scale.setScalar(Math.abs(Math.cos(time)) + 0.5);
    cube.material.opacity = (Math.sin(time) + 1) / 2;

    renderer.render(scene, camera);
}

animate();

Использование одного clock для всех параметров обеспечивает согласованность анимации и упрощает контроль над сложными эффектами.


Анимация с помощью ключевых кадров

Three.js поддерживает анимации на основе ключевых кадров через THREE.KeyframeTrack и THREE.AnimationClip. Это особенно полезно для повторяющихся или заранее рассчитанных анимаций:

const times = [0, 1, 2, 3]; // секунды
const values = [0, 0, 0, 0, 2, 0, 0, 0, 0, 0, -2, 0]; // координаты x, y, z

const positionTrack = new THREE.VectorKeyframeTrack('.position', times, values);
const clip = new THREE.AnimationClip('moveCube', -1, [positionTrack]);

const mixer = new THREE.AnimationMixer(cube);
const action = mixer.clipAction(clip);
action.play();

function animate() {
    requestAnimationFrame(animate);
    const delta = clock.getDelta();
    mixer.update(delta);
    renderer.render(scene, camera);
}

animate();

AnimationMixer позволяет управлять воспроизведением, паузой и смешиванием нескольких клипов, создавая сложные сцены с независимыми анимационными слоями.


Практика оптимизации

Для плавной анимации на больших сценах важно:

  1. Минимизировать сложные вычисления в animate() — заранее вычислять значения вне цикла, использовать lookup-таблицы.
  2. Использовать requestAnimationFrame, а не setInterval, для синхронизации с частотой обновления экрана.
  3. Делать анимацию фрагментами, обновляя только видимые объекты.
  4. Применять Clock и delta для независимости от FPS.

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