3D 的加载预算:分包、可见才加载、以及什么时候该停下

一个 3D 页面有三笔成本 —— 下载、首帧编译、每帧绘制。三笔都要单独设预算,任何一笔失控都会让「好看」变成「卡」。

一个 3D 页面的成本分三笔,必须分开算。

第一笔:下载

浮岛的场景 chunk 是 582.9 KB(gzip 后约 150 KB)。这笔钱只该由打开浮岛页的人付,而且要等到真的需要时再付。做法有三层:

  1. 按页分包。页面 HTML 里只有 1.2 KB 的引导脚本,场景通过动态 import() 进来,不写进 HTML,也不预加载。
  2. 可见才加载。用 IntersectionObserver 盯着舞台,带 200px 提前量;用户没滚到,一个字节都不下载。
  3. 空闲才加载。可见之后再等一次 requestIdleCallback(Safari 没有,退化成 200ms 延时),避开首屏的 CSS、字体与首屏脚本。

三道闸加起来的效果是:其他四百多个页面里没有 three,浮岛页的首屏也不和它抢带宽。

第二笔:首帧编译

着色器编译发生在第一次渲染,如果不管它,用户看到的是「骨架 → 卡一下 → 出现」。所以在挂载前先预编译:

renderer.compile(scene, camera);

同时把加载态交给 HTML 骨架,而不是脚本生成:骨架在文档里,高度由 clamp() 定死,所以脚本到位之前页面高度就是最终高度,不会把下面的内容顶下去。

第三笔:每帧

项 值 控制手段
绘制调用 约 380 实例化(草、花、苔石各一次)
三角面 约 5 万 低多边形加平面着色,靠顶点色而不是贴图
阴影 只算一次 shadowMap.autoUpdate = false
像素比 上限 1.75 高 DPI 屏上不追 3 倍
后处理 泛光一档 不做多通道

另外,prefers-reduced-motion 打开时停掉动画但保留交互:自动环绕停、花瓣停、水面的 time 不再推进,但拖拽、缩放、机位切换照常。这不是性能优化,是尊重用户的设置。

失败与降级

WebGL 起不来,就显示一句话和一个重试按钮,而不是一片空白;webglcontextlost 时停掉动画循环。这两条都不难,但决定了页面在坏环境里是「坏了」还是「能用」。

什么时候该停下

如果 3D 只是装饰 —— 比如文章里的一张配图 —— 就不要把它放在首屏,也不要为了它给全站加依赖。目的地型页面才值得:用户是为了看这个东西来的,他愿意等 150 KB。

反过来,如果一个页面需要滚动三屏才看到 3D,而 3D 又不是它存在的理由,那正确的做法是不做,而不是优化。

收尾

3D 的性能问题很少出在渲染器上,多数出在没有给三笔成本分别设预算:下载不管、首帧不管、每帧靠感觉调。把三笔分开写清楚,剩下的事情都是局部的。

← 返回文章列表

评论

…