探索 visionOS 中的沉浸式网站环境
Explore immersive website environments in visionOS
2026年6月10日
一句话判断
网页终于能原生“击穿”浏览器边界做 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 的变换顺序很敏感,translateSelf 和 rotateSelf 的顺序写反了,模型就会绕着错误的轴心旋转。
最佳实践
- 资产加载策略:如果你的网页只是提供一个“进入密室”的按钮,不需要在页面上展示 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 内置动画。