1.0x
Diary Sectionの開発記録

Diary Sectionの開発記録

2026-02-14 ~ 2026-02-20

背景

ブログの日記投稿に使う画像ギャラリーを作りたかった。
ブログを放置していたが、旧正月の連休にすることがなくて始めた。

このテンプレートを見て、ブログ記事でもこのように画像と文章を動きのある形で表示したくなった。

diary-section-sample

Three.jsで実装するとよさそうだと思ったものの、自分はThree.jsを知らなかったので、
AI(Claude Code)を使って実装することにした。

結論から言うと、試行錯誤を経て最もシンプルな形に落ち着いた。

  1. Three.jsで3D空間に画像を配置
  2. CSS 3D Transform
  3. 統合Three.jsギャラリー
  4. Three.jsリングカルーセル
  5. シンプルな画像カルーセル。

1. Three.js(WebGL)版

最初に@react-three/fiberベースで実装した版。

実装内容

  • WebGL Canvas上に画像を3D planeとして配置
  • スクロール進捗に応じてplaneがY軸方向に移動(parallax)
  • スクロールに合わせたカメラのdollyとvertical travel
  • smoothstepベースのfade in/out
  • alphaMap DataTextureによる画像端のsoft fade
  • fogで遠くの画像が自然に消える

問題点

  • Three.jsのインスタンスが一つなら問題なかったが、日記ページで9個のDiarySectionがそれぞれ独立したWebGL Canvasを作ると、GPUコンテキスト数の上限に達した
  • 画像配置とカメラの動線が不自然
  • モバイルで深刻な性能低下
  • スクロールの引っ掛かりがひどく、目立つフレーム落ちが発生
  • 下のデモはインスタンスが一つなので性能問題は全くない。実際の日記ページのように9個が同時に存在すると問題が現れる

0 / 4

Three.js版デモ

スクロールすると、WebGL Canvas上の3D空間で画像が動きます。
画像が不自然に重なる部分があり、少し気になります。
ここは画像が4枚だけなので性能に問題はありませんが、日記投稿には通常20枚ほどの画像があり、 その場合は大幅なフレーム落ちが発生しました。


2. CSS 3D Transform版

Three.js版での経験を基に、CSS 3D transformとframer-motionで再実装。 先にThree.jsで作ったことで、3Dレイアウトとスクロール操作の方向性が決まっていたため、
CSS 3Dに移すと、目指す形にある程度近づけた。

実装内容

  • CSSのperspectiveとtranslateZ / rotateYによる奥行き
  • framer-motionのuseScrollとuseTransformによるスクロール連動アニメーション
  • 画像ごとの独立したスクロール区間による順次fade in/out
  • コンテナのscaleとtranslateYによるカメラのシミュレーション
  • mask-imageのグラデーションによる画像端のsoft fade
  • radial gradient fog (vignette)
  • focus modeの切り替え、サムネイルストリップ、ライトボックス

Three.jsからの改善点

  • WebGL Canvasを使わずブラウザーのcompositorによるGPUアクセラレーションだけを使うため、コンテキスト数の上限問題が解消
  • 同じページに複数のセクションがあっても性能問題なし
  • 追加最適化は必要だが、Three.js版よりある程度滑らかにスクロールする

0 / 4

浅草
すき家の牛丼
神馬あかりちゃん
神田神社

CSS 3D版デモ

スクロールすると、perspectiveとtranslateZで奥行きを付けた画像が順番に現れます。 画像が不自然に重なる問題は残っています。
画像の位置とサイズがランダムに決まるため、一部のコンテンツが正しく見えない問題があります。


3. 統合Three.jsギャラリー

リングカルーセルの「複数Canvas」が大きな負荷を生むと考え、 ページ全体を一つのThree.js Canvasに統合する方法も試した。

コンセプト

  • DiaryGalleryUnified: 全セクションの画像を一つのCanvasに描画
  • セクション間の切り替えをカメラ移動で処理
  • useUnifiedScrollフックでスクロール進捗をセクション番号に対応付け
  • テクスチャキャッシュの共有でメモリーを効率化

採用しなかった理由

  • Astroのislandアーキテクチャと根本的に衝突。client:visibleで遅延hydrationする構成と単一Canvasを両立できない
  • ページに入ったときに全画像のテクスチャを一度に読み込む必要がある
  • 3D Canvasに文章を重ねることによるアクセシビリティの問題

0 /4


4. Three.jsリングカルーセル

CSS 3D版には実装上の限界があり、最初のThree.js版はモバイルでクラッシュが頻発したため、
テンプレートのように多数の写真を入れてギャラリー風に見せるのは難しいと判断した。 別の方向を試そうとサンプルを探し、Three.jsのリング型カルーセルの例を見つけた。

コンセプト

  • @react-three/fiberベースの3Dリング型カルーセル
  • 画像を円筒状に配置し、セクションのドットを押すとリングが回転
  • 高さcalc(100dvh - 150px)の没入型ビュー
  • alphaMapによる画像端のsoft fade
  • スクロールではなくクリックによるナビゲーション

実装の試み

  • CarouselRing.tsx: R3F Scene — 画像を円形に配置し、atan2でカメラを向くように回転
  • UnifiedImagePlane.tsx: 各画像のplane — テクスチャ読み込み、alphaMap、opacityのアニメーション
  • buildCarouselLayout.ts: 円形配置の計算(半径・角度)
  • carouselAlphaMap.ts: soft-edge DataTextureを生成(RGBAFormatが必須。GREENチャンネルの問題を学んだ)
  • texturePreload.ts: 同時実行数を制限したテクスチャローダー、createImageBitmapとcanvasへのフォールバック

0 / 6


5. 最終版: DiaryCarousel

Three.jsリングカルーセルは性能問題もある程度解決し、モバイルでの動作にも問題はなかった。
ただ、スクロールや見せ方はPCのブラウザーではよかったものの、 モバイルではスクロールもコンテンツの閲覧もしにくく、ここまでして実装する意味があるのか疑問に感じた。

そんな中、フォローしているanthony-fuのブログで、ある記事がカルーセルと文章だけで構成されているのを見た。
わざわざ複雑にする必要はないのではと思い、モバイルではむしろ単純な方が見やすいため、シンプルにすることに決めた。

anthony-fu

実装内容

  • embla-carousel-reactベースのカルーセル(既存のui/carouselコンポーネントを再利用)
  • 画像をクリックしてライトボックスを開く(ImageLightbox)
  • 動画対応(videos prop)
  • カウンターバッジ(現在のスライド / 全体)
  • デスクトップは左右の矢印、モバイルはスワイプ

利点

  • 追加バンドルなし。既存のCarouselとImageLightboxを組み合わせる
  • 100行未満の単一コンポーネント
  • 3Dギャラリーよりバンドルサイズを大幅に削減(Three.js・R3F・framer-motionの3Dロジックを除去)
  • ネイティブに近いモバイルのスワイプ操作
  • client:visibleと自己終了JSXでMDXの記述が簡単
  • キーボード操作とスクリーンリーダーに対応し、アクセシビリティも良好
浅草
すき家の牛丼
神馬あかりちゃん
神田神社