You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Three.js实现相机补间动画时初始位置出现闪烁问题

问题:OrbitControls平移相机后Tween动画初始位置闪烁

我开发了一个基于Three.js的简单程序,可将相机通过补间动画移动到指定位置,正常运行时效果良好。但当使用OrbitControls平移相机后再触发补间动画,动画初始阶段会出现位置闪烁问题。

当前代码:

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const TWEEN = require('@tweenjs/tween.js');

const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({ canvas });
const canvasWid = Math.floor(window.innerWidth * 2 / 3), canvasHei = (window.innerHeight * 2 / 3);
renderer.setSize(canvasWid, canvasHei);
var scene = new THREE.Scene();
scene.background = new THREE.Color('grey');
scene.add(new THREE.HemisphereLight(0xB1E1FF, 0xB97A20, 1));

const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(10, 15, 20);

const controls = new OrbitControls(camera, canvas);
controls.screenSpacePanning = true;
controls.target.set(0, 0, 0);
controls.update();


const geometry = new THREE.BoxGeometry( 4, 4, 4 );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );

var tween;
var tweening = false;
function animate() {
    requestAnimationFrame(animate);
    if (tweening) {
        TWEEN.update();
        camera.lookAt(new THREE.Vector3(0, 0, 0));
    }
    renderer.render(scene, camera);
}
animate();

window.addEventListener('keydown', keyDown);
function keyDown(e) {
    if (e.key === "a") {
        var vv = new THREE.Vector3(prompt(), prompt(), prompt());
        tween = new TWEEN.Tween(camera.position).to(vv, 3000)
            .easing(TWEEN.Easing.Quadratic.InOut)
            .start();

        tweening = true;
        tween.onComplete(function () {
            tweening = false;
        });
    }
}

问题原因

OrbitControls在交互后会持续维护相机状态(即使无交互,启用阻尼时也会逐帧更新),启动Tween动画时,两者会同时修改相机position属性,导致帧间位置突变,引发闪烁。

修复方案

通过禁用OrbitControls避免与Tween冲突,同时优化动画逻辑确保状态同步:

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const TWEEN = require('@tweenjs/tween.js');

const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({ canvas });
const canvasWid = Math.floor(window.innerWidth * 2 / 3), canvasHei = (window.innerHeight * 2 / 3);
renderer.setSize(canvasWid, canvasHei);
var scene = new THREE.Scene();
scene.background = new THREE.Color('grey');
scene.add(new THREE.HemisphereLight(0xB1E1FF, 0xB97A20, 1));

const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(10, 15, 20);

const controls = new OrbitControls(camera, canvas);
controls.screenSpacePanning = true;
controls.target.set(0, 0, 0);
controls.update();

const geometry = new THREE.BoxGeometry(4, 4, 4);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

function animate() {
    requestAnimationFrame(animate);
    // 始终更新TWEEN,保证动画正常运行
    TWEEN.update();
    // 仅当控制器启用时更新其状态
    if (controls.enabled) {
        controls.update();
    }
    renderer.render(scene, camera);
}
animate();

window.addEventListener('keydown', keyDown);
function keyDown(e) {
    if (e.key === "a") {
        const targetPos = new THREE.Vector3(
            parseFloat(prompt('X坐标')),
            parseFloat(prompt('Y坐标')),
            parseFloat(prompt('Z坐标'))
        );

        // 禁用控制器,防止干扰补间动画
        controls.enabled = false;

        // 以相机当前实时位置为补间起始点
        tween = new TWEEN.Tween(camera.position.clone())
            .to(targetPos, 3000)
            .easing(TWEEN.Easing.Quadratic.InOut)
            .onUpdate(function(currentPos) {
                // 更新相机位置
                camera.position.copy(currentPos);
                // 确保相机始终看向目标点
                camera.lookAt(controls.target);
            })
            .onComplete(function() {
                // 动画结束后重新启用控制器
                controls.enabled = true;
                // 同步控制器与相机状态,避免后续交互异常
                controls.update();
            })
            .start();
    }
}

关键修改说明

  • 禁用控制器:补间启动时关闭controls.enabled,避免两者同时修改相机位置
  • 实时起始位置:用camera.position.clone()作为补间起点,确保读取的是相机当前真实位置
  • 逐帧更新朝向:将camera.lookAt移到onUpdate回调,每次位置变化后立即调整朝向
  • 状态同步:动画结束后启用控制器并调用controls.update(),保证后续交互正常

内容的提问来源于stack exchange,提问作者Luca Phillips

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 16:40:40