如何在THREE.js中实现点击拖拽交互
THREE.js 最简点击拖拽实现(无需RayCasting)
如果你想避开复杂的RayCasting,实现类似旧版THREE.interaction的简单点击拖拽,核心思路是利用DOM鼠标事件+坐标映射,直接通过鼠标位置计算对象的世界坐标偏移,无需射线检测。以下是具体实现:
核心逻辑
- 记录拖拽状态、目标对象、鼠标与对象的初始位置
- 将鼠标屏幕坐标转换为THREE.js的标准化设备坐标,再映射为世界坐标
- 通过初始与当前鼠标的世界坐标差,计算对象的拖拽偏移量
代码示例(正交相机场景)
正交相机的坐标映射更直接,适合2D或平面拖拽场景:
// 基础场景初始化 const scene = new THREE.Scene(); const camera = new THREE.OrthographicCamera( window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, 1, 1000 ); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 创建可拖拽立方体 const cube = new THREE.Mesh( new THREE.BoxGeometry(50, 50, 50), new THREE.MeshBasicMaterial({ color: 0x00ff00 }) ); scene.add(cube); camera.position.z = 5; // 拖拽状态变量 let isDragging = false; let dragTarget = null; const mouseStart = new THREE.Vector2(); const objectStart = new THREE.Vector3(); // 鼠标按下:启动拖拽 renderer.domElement.addEventListener('mousedown', (e) => { // 这里可以直接指定拖拽目标,或用 bounding box 简单判断点击范围 dragTarget = cube; isDragging = true; // 记录鼠标初始位置(转标准化设备坐标) mouseStart.x = (e.clientX / window.innerWidth) * 2 - 1; mouseStart.y = -(e.clientY / window.innerHeight) * 2 + 1; // 记录对象初始位置 objectStart.copy(dragTarget.position); }); // 鼠标移动:执行拖拽 renderer.domElement.addEventListener('mousemove', (e) => { if (!isDragging || !dragTarget) return; // 当前鼠标标准化坐标 const mouseCurrent = new THREE.Vector2( (e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1 ); // 转换为世界坐标 const startWorld = new THREE.Vector3(mouseStart.x, mouseStart.y, 0).unproject(camera); const currentWorld = new THREE.Vector3(mouseCurrent.x, mouseCurrent.y, 0).unproject(camera); // 计算偏移并更新对象位置 const offset = currentWorld.sub(startWorld); dragTarget.position.copy(objectStart.clone().add(offset)); }); // 鼠标松开:结束拖拽 renderer.domElement.addEventListener('mouseup', () => { isDragging = false; dragTarget = null; }); // 渲染循环 function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } animate();
扩展:简单拾取判断(无需RayCasting)
如果需要判断点击到对象才启动拖拽,可以用对象的boundingBox做范围检测,比射线检测轻量:
// 在mousedown事件中替换拖拽目标赋值逻辑 renderer.domElement.addEventListener('mousedown', (e) => { // 鼠标转世界坐标 mouseStart.x = (e.clientX / window.innerWidth) * 2 - 1; mouseStart.y = -(e.clientY / window.innerHeight) * 2 + 1; const mouseWorld = new THREE.Vector3(mouseStart.x, mouseStart.y, 0).unproject(camera); // 计算对象的世界空间包围盒 cube.geometry.computeBoundingBox(); const worldBox = cube.geometry.boundingBox.clone(); worldBox.applyMatrix4(cube.matrixWorld); // 判断鼠标是否在包围盒内 if (worldBox.containsPoint(mouseWorld)) { dragTarget = cube; isDragging = true; objectStart.copy(dragTarget.position); } });
透视相机适配
如果用透视相机,只需将坐标转换时的z值设置为对象的z坐标(确保鼠标坐标映射到对象所在的深度平面):
// 转换世界坐标时指定对象的z值 const startWorld = new THREE.Vector3(mouseStart.x, mouseStart.y, cube.position.z).unproject(camera); const currentWorld = new THREE.Vector3(mouseCurrent.x, mouseCurrent.y, cube.position.z).unproject(camera);
这种方式无需依赖复杂的射线检测,代码简洁,适合简单拖拽场景,完全可以替代旧版THREE.interaction的基础功能。
内容的提问来源于stack exchange,提问作者Om Solari
相关产品推荐
相关产品推荐

