1.0x
Diary Section Development Notes

Diary Section Development Notes

2026-02-14 ~ 2026-02-20

Background

I wanted to create an image gallery for diary posts on the blog.
I had been neglecting the blog and started during the Lunar New Year holiday because I had nothing else to do.

After seeing this template, I wanted to present images and writing dynamically inside blog posts in the same way.

diary-section-sample

I thought Three.js would be a good fit, but I did not actually know Three.js,
so I decided to implement it with AI (Claude Code).

To give away the ending, trial and error led to the simplest approach:

  1. Place images in 3D space with Three.js
  2. CSS 3D Transform
  3. Unified Three.js gallery
  4. Three.js ring carousel
  5. Simple image carousel.

1. Three.js (WebGL) version

The first version was built with @react-three/fiber.

Implementation

  • Images placed as 3D planes on a WebGL Canvas
  • Planes move along the Y axis with scroll progress (parallax)
  • Camera dolly and vertical travel linked to scrolling
  • Fade in/out based on smoothstep
  • Soft fades at image edges using alphaMap DataTexture
  • Fog makes distant images disappear naturally

Problems

  • A single Three.js instance worked fine, but nine DiarySection components each creating an independent WebGL Canvas on a diary page hit the GPU context limit.
  • Image arrangement and camera movement felt unnatural
  • Severe performance degradation on mobile
  • Heavy scroll stuttering and noticeable frame drops
  • The demo below has only one instance, so it has no performance problems; the issues appear with nine instances at once, as on the actual diary page.

0 / 4

Three.js version demo

Scrolling moves images through 3D space on the WebGL Canvas.
Some images overlap unnaturally, which is a little distracting.
There are only four images here, so performance is fine, but a diary post usually contains about 20 images, and that caused substantial frame drops.


2. CSS 3D Transform version

Reimplemented with CSS 3D transforms and framer-motion based on the Three.js experience. The first Three.js attempt had already clarified the direction for the 3D layout and scroll interactions,
so moving to CSS 3D brought the result reasonably close to what I wanted.

Implementation

  • 3D depth with CSS perspective and translateZ / rotateY
  • Scroll-driven animation with framer-motion useScroll and useTransform
  • Sequential fade in/out using independent scroll windows for each image
  • Camera simulation through container scale and translateY
  • Soft edge fade using a mask-image gradient
  • radial gradient fog (vignette)
  • Focus mode toggle, thumbnail strip, and lightbox

Improvements over Three.js

  • Uses only browser compositor GPU acceleration without a WebGL Canvas, resolving the context-limit issue
  • No performance problems when multiple sections share a page
  • Further optimization is needed, but scrolling is somewhat smoother than the Three.js version

0 / 4

Asakusa
Sukiya gyudon
Sacred horse Akari-chan
Kanda Shrine

CSS 3D version demo

As you scroll, images with depth created by perspective and translateZ appear in sequence. Unnatural image overlap still remains.
Random image positions and sizes mean that certain content is not displayed properly.


Believing multiple canvases in the ring carousel caused substantial performance overhead, I also tried unifying the entire page in a single Three.js Canvas.

Concept

  • DiaryGalleryUnified: Render images from all sections in a single Canvas
  • Camera movement handles transitions between sections
  • useUnifiedScroll maps scroll progress to section indices
  • Shared texture cache improves memory efficiency

Why it was abandoned

  • Fundamentally clashes with Astro’s island architecture: lazy hydration through client:visible cannot coexist with a single Canvas
  • All image textures need to load at once on page entry
  • Accessibility issues with overlaying text content over a 3D Canvas

0 /4


CSS 3D had implementation limits, and the first Three.js attempt crashed too frequently on mobile.
I concluded that displaying many photos like a gallery in the example template was not feasible, so I looked for a different direction and found a Three.js ring carousel example.

Concept

  • 3D ring (circular) carousel based on @react-three/fiber
  • Images arranged around a cylinder; clicking section dots rotates the ring
  • Immersive view with a height of calc(100dvh - 150px)
  • Soft image-edge fading with alphaMap
  • Click-based navigation instead of scrolling

Implementation attempts

  • CarouselRing.tsx: R3F Scene — circular image arrangement, rotated toward the camera using atan2
  • UnifiedImagePlane.tsx: Individual image planes — texture loading, alphaMap, and opacity animation
  • buildCarouselLayout.ts: Circular layout mathematics (radius and angle calculations)
  • carouselAlphaMap.ts: Generate soft-edge DataTexture (RGBAFormat required — learned about the GREEN channel issue)
  • texturePreload.ts: Concurrency-limited texture loader, createImageBitmap with canvas fallback

0 / 6


5. Final version — DiaryCarousel

The Three.js ring carousel resolved the performance issues to some extent and worked on mobile.
Its scrolling and presentation were fine in a PC browser, but on mobile, scrolling and viewing content were awkward, making me question whether this approach was worthwhile.

Then I saw a post on the blog of anthony-fu, whom I follow, consisting simply of a carousel and text paragraphs.
That made me wonder whether complexity was necessary at all. A simple approach was easier to view on mobile, so I decided to keep it simple.

anthony-fu

Implementation

  • Carousel based on embla-carousel-react (reusing the project’s existing ui/carousel component)
  • Click an image to open a lightbox (ImageLightbox)
  • Video support (videos prop)
  • Counter badge (current slide / total)
  • Desktop: left/right arrows; mobile: swipe

Advantages

  • No additional bundle — combines existing Carousel and ImageLightbox
  • A single component under 100 lines
  • Much smaller bundle than the 3D gallery (removed Three.js, R3F, and framer-motion 3D logic)
  • Mobile swipe UX close to native
  • Simple MDX authoring with client:visible and self-closing JSX
  • Good accessibility — keyboard navigation and screen-reader support
Asakusa
Sukiya gyudon
Sacred horse Akari-chan
Kanda Shrine