← 返回文章
#前端#离线#查看器#浏览器 API

不 Fork 官方 Viewer:pkg:// 四层 URL 重映射离线桥

官方查看器假设资源都通过 HTTP 加载。要在纯静态页面里离线打开本地目录,我们用一个运行时 URL 桥把相对路径请求映射回内存中的文件,全程不修改官方代码。

本文目录

场景

我们希望用户把一个产物目录拖进页面就能直接查看,不需要启动服务器。目录里有 settings.json、lod/lod-meta.json,以及成百上千个分块文件和体素碰撞数据。

官方 supersplat-viewer 在加载时并不认为自己在处理本地文件。它会用 new URL(chunkRelPath, contentUrl) 拼出每个分块的地址,然后用 fetch、XMLHttpRequest 或 <img> 去请求。对于普通的 Blob URL,这些相对子路径无法解析。

思路:虚拟协议 + 四层拦截

我们注册一个虚拟协议 pkg://<rootId>/<相对路径>,把拖入的目录登记为一个根,并在查看器的生命周期内拦截四类请求入口:

  1. window.fetch
  2. XMLHttpRequest.open
  3. HTMLImageElement.src
  4. HTMLMediaElement.src

拦截后,请求的 pkg:// 地址会被映射回内存中对应的 File 或 Blob。官方代码发出的仍然是普通的 URL 请求,因此无需理解这个虚拟协议。

为什么是四层

只拦截 fetch 不够:官方的分块图片通过 <img> 加载,体素碰撞数据则可能通过 XHR 读取。每一层都需要单独覆盖,否则会有一部分资源仍然指向无效地址。把四层放在同一个桥模块里统一管理,便于在生命周期结束时全部还原。

不 Fork 的边界

这个方案的前提是官方包以黑盒 npm 依赖的形式引入。我们只使用它公开的入口:创建查看器的函数、查看器句柄、样式文件,以及 settings 的校验与迁移函数。

为了防止升级时悄悄破坏这些边界,项目中有一个 API 面审计脚本:它扫描源码实际用到的官方符号,生成一份清单。升级官方版本时,只需更新依赖,然后对照类型声明检查清单即可。维护成本基本集中在这份清单上,而不是散落在源码的修改里。

少数官方不支持的内容(如 GLB 网格、骨骼动画角色与程序化几何体)通过查看器暴露的应用对象挂入场景根,这同样属于公开接口的使用,而不是修改内部实现。

副作用与注意事项

  • 生命周期要严格:拦截会改写全局对象,离开页面或卸载查看器时必须还原。否则同一页面中的其他代码会收到意外的请求结果。
  • 加载揭示特效可关闭:查看器的加载揭示效果默认开启,可以通过 ?noreveal 参数关闭,便于调试。

小结

当官方组件不假设你的运行环境时,最稳妥的做法通常不是 fork,而是在它依赖的边界上做一层适配。这一层越薄、越有明确的入口与出口,升级成本就越低。

OPEN TO WORK

有类似的工程问题?

3DGS 数据处理、流式加载与查看器定制,欢迎交流或合作。