dom元素需锚定xr参考空间并实时投影至视口坐标,通过webxr device api监听帧姿态、控制器射线与ui平面交点,用translate3d定位、禁用canvas指针事件实现原生vr交互。

直接在 DOM 上构建原始 VR 交互层,不是把按钮拖进 VR 场景,而是让 DOM 元素“浮”在 VR 画面之上,响应头部转动与控制器指向——这需要 WebXR Device API 的底层控制能力,而非仅依赖 Three.js 封装。
理解“DOM 之上”的真实含义
所谓“DOM 之上”,指元素不参与 3D 渲染管线,而是作为 HTML 层(<div>、<code><button></button>)叠加在 XR 渲nder 输出的 canvas 顶层。它保持 CSS 布局、事件流和可访问性,但位置需实时映射到用户当前视锥中对应的空间坐标。
- 不能靠
renderer.domElement.appendChild(el)简单追加——那只是挂载在 canvas 外围,不随头显转动 - 必须监听 XR frame pose,将 3D 空间中的目标点(如 UI 锚点)投影到 2D 视口坐标,再用
el.style.transform: translate3d()定位 - 需禁用 pointer-events 在 canvas 上,把点击穿透给 DOM 层处理,同时用
getViewerPose()判断是否正对 UI 区域
启用 XR 参考空间并绑定 DOM 元素
DOM UI 不是静态浮动,它需锚定在某个空间参考系中(如 'local-floor'),才能随用户移动保持相对位置。例如:一个悬浮在用户正前方 2 米处的菜单,应始终位于 floor 平面上方 1.5 米高。
- 创建 session 后,调用
session.requestReferenceSpace('local-floor')获取稳定参考空间 - 为每个 DOM UI 元素定义其在该空间中的
position和orientation(单位:米 + 四元数) - 在每一帧中,用
frame.getViewerPose(referenceSpace)获取当前视角姿态,再通过viewerPose.transform.inverse().transformPoint(anchorPoint)投影出屏幕坐标
实现控制器指向与 DOM 交互
用户用控制器激光“瞄准” DOM 按钮时,不能只检测 canvas 内的 raycast——要将控制器在 XR 空间中的射线,反向投影到 DOM 层的二维平面,再做碰撞判断。
- 从
frame.getInputSources()获取控制器 pose,构造一条从手部出发、沿前向量延伸的射线 - 使用
frame.getHitTestResultsForInputSource(hitTestSource, inputSource)检测射线与虚拟平面交点(即使无 AR 功能,VR 中也可设一个隐式 UI 平面) - 将交点转换为归一化设备坐标(NDC),再映射为 DOM 容器内的像素位置,触发
el.dispatchEvent(new MouseEvent('click')) - 添加视觉反馈:动态修改
el.style.boxShadow或插入<span class="reticle"></span>跟随光标
性能与兼容性关键点
DOM 层叠加在 XR 渲染上,极易因重排重绘拖慢帧率。90fps 是硬门槛,任何 layout thrashing 都会导致眩晕。
- 所有 UI 定位必须用
transform: translate3d(x, y, 0),避免触发布局计算 - 使用
session.requestAnimationFrame()替代requestAnimationFrame(),确保 DOM 更新与 XR 渲染严格同步 - 在 iOS Safari 或旧版 Android Chrome 上,
navigator.xr.isSessionSupported('immersive-vr')必须显式探测;若返回 false,降级为 inline 模式并隐藏 VR UI - canvas 初始化时必须传入
{ xrCompatible: true },否则 DOM 层无法正确叠加在立体渲染输出上











