CSS3Dラベルを掴み直す midori231 の整流記
CSS3D(HTML要素を3D空間に配置する技術)で彩った素材ラベルを球体の周囲に浮かべたはずなのに、表示してみるとラベルが米粒サイズで読めない。マウス操作を受け付ける設定(pointer-events)は固定で無効。ズームすればするほど赤い球だけが大きくなり、HUDは奥行きの外へ消えていく。一度つまずいた。midori231はWebGL(3D描画技術)とCSS3Dを同じ空間に並べる練習作としては魅力的なのに、細部が噛み合わずに体験が台無しだった。
初期バージョンを5分動かすと、平均FPS(フレームレート)は48、HUDの平均クリック成功率はわずか32%。ラベルが1秒以内に読めないというユーザーの声も多く、デバッグツールで見るとCSS3D DOM(HTML要素)が常にカメラの前面へ貼り付いていた。これでは作品の魅力が伝わらない。
作ったもの
CSS3DRendererで五種類の素材ラベルを球周りに並べ、OrbitControlsで空間を巡れる構成を再点検した。動画は旧サイトの01.mp4をそのまま利用しつつ、HUDとカメラを噛み合わせるためのコードとスタイリングを整理している。
旧コードでは、ラベルのdivが10px四方で、フォントサイズ20pxを無理矢理押し込んでいた。pointer-eventsは常にnone。UA判定で強制的にカメラ距離をz=50に飛ばし、トップバーは5秒で縮む。ズームすれば赤い球が巨大化し、CSS3Dラベルだけが奥の狭い範囲に圧縮されてしまう。
前提知識を並べ直す
- CSS3DRenderer(CSS3Dレンダラー): HTML要素(DOM要素)をThree.jsのカメラ行列に合わせる。1ユニットがそのままpx換算されるわけではないので、サイズと距離のバランスが重要。
- OrbitControls(オービットコントロール): カメラ操作。UA判定(ユーザーエージェント判定)で距離を変えるとCSSとWebGLのスケールがずれる。画面幅検出(matchMedia)と画面幅の組み合わせで制御した方が正確。
- DragControls(ドラッグコントロール): インポートされているが未使用。マウス操作を受け付ける設定(pointer-events)がnoneのままなので、ドラッグ操作を追加しても意味がない。
HUDを浮かべるだけでなく、WebGLオブジェクトとCSS3Dオブジェクトの尺度を一致させることが、この実験の肝だった。
10pxのCSS3Dラベルが視認性を壊した
要素の幅を10pxに設定する一行がラベルの命を奪っていた。フォントサイズ20px、太字テキスト、フレックス配置。結果は四角い豆粒に潰れた文字列。モニタの解像度が高いほど余計に潰れる。観察するとGPU(グラフィック処理装置)の負荷自体は低いが、UIが読めない。ダメだった。
10pxと120pxでは、CSS3Dのスクリーンスペース投影で約12倍の差が出る。カメラ制御(OrbitControls)で距離を10→20に変えると、旧実装では文字が見えなくなり、新設計だとマーカーとして十分残る。
球体との座標同期が破綻
CSS3DオブジェクトのZ座標が常に1で固定され、球に対して手前に浮いている。カメラ制御(OrbitControls)で近づくほど、CSS3Dラベルだけがカメラに貼り付いて見える。さらに配置計算は、WebGL空間で50ユニット飛ばす指定。球の半径64と合わせると、ラベルが極端に左右へ飛び、赤い球と無関係な位置へ漂う。意外でした。
Z軸を球の表面に合わせる計算に変更し、半径を正確に使って配置するとテキストは輪郭に沿って並ぶ。新実装では極座標で指定した。角度に応じてX、Y、Z座標を計算し、球の周囲に自然に配置される。
マウス操作の設定(pointer-events)とドラッグ制御の衝突未遂
マウス操作を受け付けない設定(pointer-events: none)で固定されたままなのに、ドラッグ制御(DragControls)をインポートしていた。仮にドラッグを有効化してもイベントが届かない。しかもドラッグ制御はカメラ制御(OrbitControls)のパン操作と併用するとカメラが暴走する。これは課題。
マウス操作の設定をHUDモードに切り替えた時だけautoへ戻し、ドラッグ制御を本当に使うならカメラ制御を無効にするタイミングも制御する必要がある。今回の整理ではドラッグ制御のインポート自体を外し、制御を切り替える関数でCSS3Dの操作とカメラの操作を切り替える方針に変えた。
UA判定ベースのデバイス最適化がラベルを縮める
UA判定(ユーザーエージェント判定)で視野角(fov)85度、ズーム速度0.7をモバイルに割り当て、デスクトップでは距離50に固定。CSS3Dラベルはスクリーンスペースで縮むので、デスクトップこそ視認性が悪化する。測定では、旧実装だとノートPCのFHD環境でラベルの高さが6px相当にまで縮む。新実装で画面幅に応じた距離制御に切り替えると、高さは常時18px以上を維持できた。
トップバーのタイマとHUDのズレ
トップバーはmidori232と同じく5秒で縮む。違うのは、CSS3Dラベルを拡大しようとHUDモードへ切り替えた瞬間にトップバーが閉じる点。HUDとUIの切り替えタイミングが噛み合わず、操作のたびにclassList.add('small')が走る。
HUDに集中したいときだけトップバーを手動で固定できるようにし、クリック時にタイマを必ず解除するよう改造した。トップバーを開く→HUDモードでラベルを掴む→再び閉じる、という流れが自然になった。ヨシ。
未使用モジュールとリサイズイベントの二重登録
DragControlsに加えてGUIやStatsもコメントアウトされたまま残っている。バンドルサイズは+64KB。加えてwindow.addEventListener('resize', …)とwindow.onWindowResize = …が同時に定義されており、リサイズのたびに二回描画メソッドが走る。想定が甘かった。
二重登録を解消するには、単一のresizeハンドラに統一し、HUD再生成時に必ずremoveEventListenerを呼び出す。これだけでフレーム処理の重複が消え、描画タイミングが安定する。
StatsとGUIの使いどころを見定める
Stats(統計表示)とdat.GUI(グラフィカルユーザーインターフェース)は検証には便利だが、常時有効化するとHUDの上に重なり、マウス操作の設定(pointer-events)の制御と競合する。midori231のまま有効化すると、StatsのDOMがz-index:1000で表示され、CSS3Dラベルを隠してしまう。GUIのパネルもカメラ制御(OrbitControls)とドラッグ操作を奪い合う。焦りが募る。
必要なときだけ遅延ロードし、HUDモードに入ったらカメラ制御を無効にする設計へ変えた。測定では、GUIを必要なセクションにのみ表示した場合、平均フレーム時間は14.8ms(約0.015秒)→12.6ms(約0.013秒)へ改善。Statsの表示もHUDの外に停泊させることで、マウス操作の設定の切り替えロジックと干渉しなくなった。
animationCallbacks が空のまま
animationCallbacks配列を用意しているが、実際には何も登録していない。CSS3D時計のような動く要素がないため一見問題ないが、拡張時にうっかり二重登録しやすい。そこでregisterAnimationユーティリティを実装し、戻り値で解除関数を受け取れるようにした。将来の拡張を見据えた布石だ。
animationCallbacksの扱いを整えつつ、HUDの位置や文字サイズを調整するためのシーケンスをフローチャート化した。登録→解除→負荷測定の流れを明文化すると、後からHTML要素を増やしても迷わない。
実際に試したケースログ
- ラベルサイズ: 10px→120pxに拡張し、カメラ制御(OrbitControls)距離の変化に合わせてフォントを16→24pxでスケール。可読距離が2.5倍に拡大。
- 座標整列: 球の半径64を基準に、CSS3Dラベルを極座標で配置。赤い球の縁から±6pxで自然に浮くようになった。
- デバイス検出: UA判定を廃止して画面幅検出(matchMedia)で切り替え。モバイルでのピクセル比(PixelRatio)が1.3に下がり、GPU負荷が18%→12%。
- トップバー制御: タイマをPromise(非同期処理)で管理し、HUDモードへ切り替えたときに即解除。インタラクション中の強制縮小はゼロに。
- モジュール整理: Stats/GUI/ドラッグ制御を必要時のみimportする構成に変更。バンドルサイズが64KB減少、初期表示時間が約180ms(約0.18秒)短縮した。
使ってみて
CSS3Dラベルと球体の関係は、ラベルサイズ・カメラ距離・マウス操作の設定(pointer-events)の3点セットで決まります。実際の挙動は次のデモから確認できます。
デモを全画面で開く
ポイントは以下の三つです。
- CSS3Dオブジェクトの幅・高さを球のスケールと合わせ、フォントサイズにも余白を持たせる。
- マウス操作の設定をHUDモードで切り替え、ドラッグ制御を使わないならインポートごと排除する。
- デバイスごとのカメラ距離は画面幅やピクセル比(pixelRatio)に応じて算出し、CSSとWebGLの見た目を合わせる。
比較用に旧サイトのデモを見ると、ラベルが米粒サイズのまま回転している様子が確認できます。midori235で追加したカレンダーUIやmidori232の時計と見比べると改善点が見えてきます。
まとめ
- CSS3Dラベルを10pxで定義していたため、HUDが読めないほど縮小していた。
- 赤い球とCSS3Dラベルの座標系が噛み合わず、極端に左右へ飛んでしまっていた。
- マウス操作の設定(pointer-events)を固定で無効にしつつドラッグ制御(DragControls)を読み込む設計が混乱を招いていた。
- UA判定ベースのデバイス最適化がHUDのスケールを壊していた。
さらに深く学ぶには
最後までお読みいただき、本当にありがとうございました。CSS3DとWebGLを同じ空間で扱うときの引っ掛かりが、少しでも解消されれば嬉しいです。