3D の読み込み予算 — チャンク分割・可視になってから読み込み・止めどき

3D ページの費用は三つあります。ダウンロード、初回フレームのコンパイル、毎フレームの描画。それぞれに別々の予算を決めないと、見栄えが引っかかりに変わります。

3D ページの費用は三つに分かれ、それぞれ別に数えなければなりません。

一つ目 — ダウンロード

浮島のシーンチャンクは 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 の性能問題はレンダラの中にではなく、三つの費用に別々の予算を置いていないことにあります。ダウンロードは野放し、初回フレームは無関心、毎フレームは感覚で調整。三つを書き出してしまえば、残りはすべて局所的な話です。

← 記事一覧に戻る

コメント

…