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
相关产品推荐
相关产品推荐

