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

求助:@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 04:53:32