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

ThreeJS多模型渲染、视角动画及鼠标无拖拽控制实现问题

问题原因
  • 鼠标坐标未归一化,直接使用像素值计算相机位置导致位移幅度过大
  • 自定义控制逻辑与OrbitControls默认拖拽交互冲突
  • 直接修改相机x/y坐标导致相机与模型距离变化,触发视觉上的缩放效果
完整修复代码

直接替换原有<script type="module">标签内的全部内容即可,已覆盖所有需求:

import * as THREE from '../build/three.module.js';
import { OrbitControls } from './jsm/controls/OrbitControls.js';
import { RoomEnvironment } from './jsm/environments/RoomEnvironment.js';
import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from './jsm/loaders/DRACOLoader.js';

let mixer, currentModel = null;
const clock = new THREE.Clock();
const container = document.getElementById( 'container' );
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

const cameraDistance = 8; // 固定相机与模型距离,避免缩放
let targetRotationX = 0;
let targetRotationY = 0;
const rotationSpeed = 0.002;

const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.outputEncoding = THREE.sRGBEncoding;
container.appendChild( renderer.domElement );

const pmremGenerator = new THREE.PMREMGenerator( renderer );

const scene = new THREE.Scene();
scene.background = new THREE.Color( 0xbfe3dd );
scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture;

const camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 100 );
camera.position.set( 5, 2, 8 );

const controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 0.5, 0 );
controls.update();
controls.enablePan = false;
controls.enableDamping = true;
controls.enabled = false; // 禁用默认拖拽交互,避免冲突

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath( 'js/libs/draco/gltf/' );

const loader = new GLTFLoader();
loader.setDRACOLoader( dracoLoader );

// 监听鼠标移动
document.addEventListener('mousemove', onDocumentMouseMove);
function onDocumentMouseMove( event ) {
    // 归一化鼠标坐标到[-1,1]范围
    mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
    // 限制旋转范围避免模型翻转
    targetRotationX = mouse.x * Math.PI * 0.3;
    targetRotationY = mouse.y * Math.PI * 0.1;
}

loadModel('models/gltf/Flamingo.glb')

function loadModel(path){
    // 旧模型渐隐
    if (currentModel) {
        currentModel.userData.fadeOut = true;
    }
    loader.load(path, function ( gltf ) {
        // 延迟300ms加载新模型,适配旧模型消失动画
        setTimeout(() => add(gltf), 300);
    }, undefined, function ( e ) {
        console.error(e);
    });
}

window.onresize = function () {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize( window.innerWidth, window.innerHeight );
};

function add(gltf){
    let model = gltf.scene;
    model.position.set( 1, 1, 0 );
    model.scale.set( 0.01, 0.01, 0.01 );
    // 初始化透明材质用于渐入
    model.traverse((child) => {
        if (child.isMesh) {
            child.material.transparent = true;
            child.material.opacity = 0;
        }
    });
    scene.add( model );
    currentModel = model;

    // 新模型渐入
    const fadeInTimer = setInterval(() => {
        let allOpaque = true;
        model.traverse((child) => {
            if (child.isMesh && child.material.opacity < 1) {
                child.material.opacity += 0.05;
                allOpaque = false;
            }
        });
        if (allOpaque) clearInterval(fadeInTimer);
    }, 16);

    mixer = new THREE.AnimationMixer( model );
    if(gltf.animations.length>0) mixer.clipAction( gltf.animations[ 0 ] ).play();

    if(!window.animateStarted) animate();
    window.animateStarted = true;
}

document.getElementById('models').addEventListener('change', function() {
    load(this.value);
});

// 按钮自动旋转函数,可直接绑定到你的按钮点击事件
function autoRotate() {
    const startTime = Date.now();
    const duration = 2000;
    const startX = targetRotationX;
    const rotateAngle = Math.PI * 2;
    function step() {
        const elapsed = Date.now() - startTime;
        if (elapsed < duration) {
            const progress = elapsed / duration;
            targetRotationX = startX + rotateAngle * progress;
            requestAnimationFrame(step);
        }
    }
    step();
}

function animate() {
    requestAnimationFrame( animate );
    const delta = clock.getDelta();
    if (mixer) mixer.update( delta );

    // 平滑更新相机位置,基于球坐标绕模型旋转
    camera.position.x = Math.sin(targetRotationX) * Math.cos(targetRotationY) * cameraDistance;
    camera.position.y = Math.sin(targetRotationY) * cameraDistance + 2;
    camera.position.z = Math.cos(targetRotationX) * Math.cos(targetRotationY) * cameraDistance;
    camera.lookAt(controls.target);

    // 鼠标悬停检测
    raycaster.setFromCamera(mouse, camera);
    if (currentModel) {
        const intersects = raycaster.intersectObject(currentModel, true);
        document.body.style.cursor = intersects.length > 0 ? 'pointer' : 'default';
        // 如需显示行动号召提示,可在此处添加对应逻辑
    }

    // 旧模型渐隐逻辑
    if (currentModel?.userData.fadeOut) {
        currentModel.traverse((child) => {
            if (child.isMesh) {
                child.material.opacity -= delta * 2;
                if (child.material.opacity <= 0) {
                    scene.remove(currentModel);
                    currentModel.userData.fadeOut = false;
                }
            }
        });
    }

    renderer.render( scene, camera );
}

内容的提问来源于stack exchange,提问作者The Old County

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 06:57:01