Explore immersive website environments in visionOS
visionOS & Spatial Computing 进阶 19m

探索 visionOS 中的沉浸式网站环境

Explore immersive website environments in visionOS

2026年6月10日

在 Apple 官方观看视频

一句话判断

网页终于能原生“击穿”浏览器边界做 VR 体验了,不用写一行 Swift 代码,几行 JS 就能把用户直接拉进 1:1 的 3D 场景里。

这场 Session 讲了什么

以前要在 visionOS 上做沉浸式体验,要么老老实实写个原生 App,要么用 WebXR 搞一堆兼容性补丁,Web 端顶多只能放个 3D 模型让用户转着看。现在 Apple 直接在 Safari 里塞了 requestImmersive() API 和 <model> 标签。网页里的 3D 模型能一键“破框”,把用户拉进真实比例的虚拟环境,而且原本的网页 UI 还能作为 2D 窗口悬浮在 3D 场景旁边。

这对做电商、文旅、营销落地页的前端和全栈开发者来说是绝杀。不用苦口婆心劝用户下载 App,在网页上点个按钮,就能让他们直接“走进”剧院看座位视角,或者“进入”密室逃脱场景看预告片。Web 的转化率优势加上空间计算的沉浸感,这波红利吃定了。

值得深挖的点

打破浏览器边界的“破框”设计

Fullscreen API 大家都熟,但它会把网页内容全屏替换掉。Apple 这次搞的 requestImmersive() 完全不同,它是把 <model> 元素单独拽出浏览器边界,铺满用户的物理空间,而 Safari 窗口本身会缩小并悬浮在场景里。这个设计非常聪明,它解决了 VR 体验中最大的痛点:上下文丢失。用户在 1:1 的剧院场景里看舞台,旁边就悬浮着选座 UI 和支付按钮,不用在“沉浸模式”和“平面模式”之间反复横跳。

不过这里有个巨大的坑:坐标系和比例的割裂。内联在网页里时,<model> 的原点在图层中心,比例跟着 CSS 走;一旦进入沉浸模式,原点瞬间变成用户的脚下(地板),比例变成真实世界的米。如果你在内联时把模型缩小了 100 倍塞进 div 里,进入沉浸模式后它不会自动放大,你得通过监听 immersivechange 事件,用 DOMMatrix 重新计算 entityTransform。Apple 在 Session 里特意强调了这点,没处理好这个矩阵变换,你的用户一进沉浸模式就会发现模型飞到天上或者缩成蚂蚁。

用 Web 思维驱动 RealityKit 资产

在 3D 场景里播放 2D 视频是个老难题,以前得写一堆 Shader 或者原生代码把视频流渲染到材质上。这次 Apple 搞了个“视频对接 (Video docking)”功能,简直是降维打击。你只需要在 Blender 里用官方插件给电视屏幕模型打上 RealityKit 注释 (Annotations),导出 USDZ。然后在网页里,直接对 <video> 标签调用 requestFullscreen(),Safari 就会自动把视频流“吸”到 3D 场景的电视屏幕上,甚至连视频的光源溢出 (Light spill) 都能自动烘焙到周围材质上。

这种把重度 3D 逻辑下沉到资产注释层,把交互逻辑留在 JS 层的做法,极大降低了门槛。前端工程师不需要懂 RealityKit 的 Entity Component System,只要会写 video.play() 就能控制 3D 场景里的电视开关。配合 Scene Understanding 组件让 Safari 窗口在 3D 地板上投射真实阴影,整个 Web 体验和原生 App 的质感几乎没区别了。

代码片段

1. 触发沉浸模式并处理坐标系切换

场景:用户点击按钮进入 3D 剧院,需要根据当前状态调整模型的位置和旋转,避免被 Safari 窗口遮挡。

const model = document.getElementById("theater");
const immersiveBtn = document.getElementById("go-immersive");

// 检查浏览器是否支持沉浸式 API
if (document.immersiveEnabled) {
    immersiveBtn.hidden = false;
}

immersiveBtn.addEventListener("click", async () => {
    // 必须在用户交互事件中触发
    await model.requestImmersive(); 
});

// 监听状态变化,重新计算模型矩阵
model.addEventListener("immersivechange", () => {
    const isImmersive = document.immersiveElement === model;
    updateModelTransform(isImmersive);
});

坑:requestImmersive() 必须在 click 等用户手势事件中调用,且进入沉浸模式后,记得在 UI 上提供明确的“退出”按钮,虽然用户也能拧数字表冠退出。

2. 内联预览时的模型矩阵调整

场景:在网页的 div 里预览剧院座位视角,需要把模型往下移 1 米,模拟人眼高度。

const theater = document.getElementById("theater");

async function updateModelTransform(isImmersive) {
    // 等待模型资产加载完毕
    await theater.ready; 
    
    const transform = new DOMMatrix();
    
    if (!isImmersive) {
        // 内联状态:原点在下,需要把模型往下移 1 米对齐人眼
        transform.translateSelf(0, -1.0, 0);
    } else {
        // 沉浸状态:原点已经在脚下,微调旋转避免被 Safari 窗口挡住
        transform.rotateSelf(0, 15, 0); 
    }
    
    // 应用变换矩阵
    theater.entityTransform = transform;
}

坑:DOMMatrix 的变换顺序很敏感,translateSelfrotateSelf 的顺序写反了,模型就会绕着错误的轴心旋转。

最佳实践

  • 资产加载策略:如果你的网页只是提供一个“进入密室”的按钮,不需要在页面上展示 3D 预览,直接把 <model> 标签的 CSS 设为 display: none。这样 Safari 不会预下载和解码几十 MB 的 USDZ 文件,直到用户真正点击 requestImmersive() 时才会加载,能省下巨大的首屏带宽。
  • 性能优化三板斧:Web 环境下的 3D 渲染预算比原生 App 抠门得多。用 usdcrush 工具压缩 USDZ 纹理;把光照直接烘焙 (Bake) 到材质贴图里,在运行时使用无光照 (Unlit) 材质;把桌面上零碎的杯子、笔合并成一个 Mesh,减少 Entity 数量。
  • 阴影投射取巧:想让 Safari 窗口在 3D 地板上投下阴影,需要给地板加 Scene Understanding 注释。千万别给高模地板加,单独建一个只有几个面的低模 (Low-poly) 地板专门用来接收阴影,不然帧率会掉得很难看。

还有什么值得关注

  • 空间照片原生支持:给 <img> 标签加个 controls 属性,就能在网页里直接调用 visionOS 的空间照片查看器,连 JS 都不用写。
  • Blender 官方插件:Apple 提供了一个 Blender 插件,可以直接在建模软件里给模型打 RealityKit 注释(比如视频对接区域、阴影接收面),不用再倒腾到 Reality Composer Pro 里了。
  • 模型动画控制:可以通过 JS 直接读取和修改 <model>currentTime 属性,像控制视频进度条一样控制 3D 场景里的开门、关灯等 USDZ 内置动画。
visionOS Safari WebXR RealityKit USDZ