В библиотеке 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 позволяет сделать
анимацию независимой от частоты кадров, что
обеспечивает плавное движение на разных устройствах.
Простое линейное изменение свойств выглядит неестественно. Для более реалистичных анимаций применяются временные функции. Например, можно использовать синусоиду, экспоненту или кубическую функцию для сглаживания движения:
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 позволяет управлять
воспроизведением, паузой и смешиванием нескольких клипов,
создавая сложные сцены с независимыми анимационными слоями.
Для плавной анимации на больших сценах важно:
animate() — заранее вычислять значения вне цикла,
использовать lookup-таблицы.requestAnimationFrame, а
не setInterval, для синхронизации с частотой обновления
экрана.Clock и delta
для независимости от FPS.Эти методы обеспечивают плавное движение и согласованное время анимации, независимо от производительности устройства.