在 Three.js 中,动画循环是实现动态场景的核心机制。requestAnimationFrame 是浏览器提供的 API,用于在下一次重绘前执行回调函数,通常以每秒 60 帧的频率运行。

1. 基础动画循环结构

1.1. 基本实现

javascript

function animate() {
    requestAnimationFrame(animate);

    // 更新场景对象
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;

    // 渲染场景
    renderer.render(scene, camera);
}
animate();

1.2. 使用时间增量(Delta Time)

为了避免不同刷新率设备上的动画速度差异:

javascript

let clock = new THREE.Clock();
let delta;

function animate() {
    requestAnimationFrame(animate);

    delta = clock.getDelta(); // 获取上一帧到当前帧的时间间隔

    // 使用时间增量使动画帧率独立
    cube.rotation.x += 1 * delta;
    cube.rotation.y += 1 * delta;

    renderer.render(scene, camera);
}

1.3. 完整的动画循环示例

javascript

// 初始化
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建一个立方体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

camera.position.z = 5;

// 动画循环变量
const clock = new THREE.Clock();
let previousTime = 0;

// 动画循环
function animate(_currentTime) {
    requestAnimationFrame(animate);

    // 计算时间增量
    const elapsedTime = clock.getElapsedTime();
    previousTime = currentTime;

    // 更新动画
    cube.rotation.x = elapsedTime * 0.5; // 基于总时间旋转
    cube.rotation.y = elapsedTime * 0.3;

    // 添加脉动效果
    cube.scale.setScalar(1 + Math.sin(elapsedTime) * 0.2);

    // 渲染
    renderer.render(scene, camera);
}

// 启动动画循环
animate();

2. 高级动画循环控制

2.1. 1. 帧率控制

javascript

class AnimationLoop {
    constructor(renderer, scene, camera) {
        this.renderer = renderer;
        this.scene = scene;
        this.camera = camera;
        this.clock = new THREE.Clock();
        this.delta = 0;
        this.fps = 60;
        this.interval = 1000 / this.fps;
        this.then = Date.now();
        this.isRunning = false;

        // 动画更新函数数组
        this.updaters = [];
    }

    addUpdate(fn) {
        this.updaters.push(fn);
    }

    start() {
        if (this.isRunning) return;
        this.isRunning = true;
        this.animate();
    }

    stop() {
        this.isRunning = false;
    }

    animate = () => {
        if (!this.isRunning) return;

        requestAnimationFrame(this.animate);

        const now = Date.now();
        this.delta = now - this.then;

        // 控制帧率
        if (this.delta > this.interval) {
            this.then = now - (this.delta % this.interval);

            // 执行所有更新函数
            this.updaters.forEach(update => {
                update(this.clock.getDelta());
            });

            this.renderer.render(this.scene, this.camera);
        }
    }
}

// 使用
const loop = new AnimationLoop(renderer, scene, camera);
loop.addUpdate((delta) => {
    cube.rotation.x += 0.5 * delta;
    cube.rotation.y += 0.3 * delta;
});
loop.start();

2.2. 2. 性能优化

javascript

function animate() {
    requestAnimationFrame(animate);

    // 只在场景需要更新时才渲染
    if (sceneNeedsUpdate) {
        updateScene();
        renderer.render(scene, camera);
        sceneNeedsUpdate = false;
    }
}

// 或者在变化时手动触发
function onModelLoaded() {
    sceneNeedsUpdate = true;
}

3. 结合动画系统(Tween.js)

javascript

import * as TWEEN from '@tweenjs/tween.js';

function animate(time) {
    requestAnimationFrame(animate);

    TWEEN.update(time); // 更新所有补间动画

    // 更新其他动画
    cube.rotation.y += 0.01;

    renderer.render(scene, camera);
}

// 创建补间动画
new TWEEN.Tween(cube.position)
    .to({ x: 2, y: 1, z: 0 }, 1000)
    .easing(TWEEN.Easing.Quadratic.Out)
    .start();

4. 最佳实践

  1. 始终使用时间增量:确保动画在所有设备上速度一致

  2. 清理资源:停止动画循环时取消 requestAnimationFrame

  3. 性能监控:使用 stats.js 监控帧率

  4. 暂停/恢复功能:实现动画循环的暂停和恢复

javascript

let animationId = null;
let isPaused = false;

function animate() {
    animationId = requestAnimationFrame(animate);

    if (isPaused) return;

    // 动画逻辑
    renderer.render(scene, camera);
}

function pauseAnimation() {
    isPaused = true;
}

function resumeAnimation() {
    isPaused = false;
}

function stopAnimation() {
    cancelAnimationFrame(animationId);
    animationId = null;
}

这些模式可以帮助你创建流畅、高效的 Three.js 动画循环。