求助:@react-three/xr复现Three.js射线附着Demo时onSelectEnd失效
解决@react-three/xr中onSelectEnd在mesh附着控制器后不触发的问题
问题核心原因:Interactive组件的交互事件是绑定在它包裹的子元素上的。当你在onSelectStart里把mesh从原来的父节点(Interactive的子节点)attach到控制器上时,这个mesh就脱离了Interactive的管辖范围,后续的onSelectEnd事件无法关联到该mesh,因此不会触发。
解决方案1:直接监听控制器的selectEnd事件
既然控制器本身的事件不受mesh位置影响,我们可以在选中mesh时,给控制器手动添加selectend事件监听,确保解绑逻辑能执行:
import { useXR } from '@react-three/xr'; // 在组件内获取场景引用(假设你的场景用ref保存) const sceneRef = useRef(); const { controllers } = useXR(); <Interactive ref={groupRef} onSelectStart={(e) => { console.log("Attach! Works!"); const controller = e.target.controller; const intersections = getIntersections(controller); if (intersections.length > 0) { const object = intersections[0].object; controller.attach(object); controller.userData.selected = object; // 绑定控制器的selectend事件 const handleSelectEnd = () => { console.log("Detach! Please!"); const selected = controller.userData.selected; if (selected) { // 将mesh移回场景(或原来的父节点) sceneRef.current.attach(selected); delete controller.userData.selected; } // 移除监听避免重复绑定 controller.removeEventListener('selectend', handleSelectEnd); }; controller.addEventListener('selectend', handleSelectEnd); } onSelectStart?.(e); }} {...rest} > {children} </Interactive>
解决方案2:不改变mesh父节点,通过帧循环跟随控制器
这种方法保留mesh在Interactive的子树中,避免事件丢失,通过在useFrame中更新mesh的位置和旋转来模拟附着效果:
import { useFrame, useRef } from '@react-three/fiber'; import { useState } from 'react'; const [selectedMesh, setSelectedMesh] = useState(null); const [activeController, setActiveController] = useState(null); // 帧循环中更新选中mesh的位置 useFrame(() => { if (selectedMesh && activeController) { // 将控制器的世界坐标转换到mesh父节点的局部空间 activeController.getWorldPosition(selectedMesh.position); activeController.getWorldQuaternion(selectedMesh.quaternion); } }); <Interactive ref={groupRef} onSelectStart={(e) => { console.log("Attach! Works!"); const controller = e.target.controller; const intersections = getIntersections(controller); if (intersections.length > 0) { setSelectedMesh(intersections[0].object); setActiveController(controller); } onSelectStart?.(e); }} onSelectEnd={(e) => { console.log("Detach! Please!"); setSelectedMesh(null); setActiveController(null); onSelectEnd?.(e); }} {...rest} > {children} </Interactive>
两种方案对比:
- 方案1更贴近Three.js原生Demo的逻辑,通过改变父节点实现附着,适合需要物理交互的场景。
- 方案2无需修改节点层级,事件绑定更稳定,适合简单的拖拽跟随场景。
内容的提问来源于stack exchange,提问作者laffan
相关产品推荐
相关产品推荐

