3 min read

Building a Japanese-inspired Snow vs. Sakura toggle with Three.js

Theme toggle: Snow vs Sakura

After years of seeing the same light/dark mode toggles everywhere, I wanted something different for my personal site. Here's how I built a Japanese-inspired seasonal theme toggle using Three.js that switches between animated snow and sakura (cherry blossom) effects.

The core concept

Rather than switching between light and dark, site toggles between:

  • A winter scene with falling snowflakes and cooler blue tones
  • A spring scene with floating cherry blossom petals and soft pink accents

Each comes with matching color schemes and ambient audio that transitions smoothly when switching.

Deep Dive - Physics & Geometry

Particle physics for snow

Snow particles in nature follow fairly predictable physics. This implementation models several key behaviors:

// Snow particle motion in the animation loop
flake.position.y -= flake.userData.speed; // Gravity effect

// Sine wave wobble using time-based oscillation
flake.position.x += Math.sin(Date.now() * flake.userData.wobbleSpeed + 
                   flake.userData.wobbleOffset) * flake.userData.wobbleAmount;

// Each flake has unique parameters
flake.userData = { 
  speed: THREE.MathUtils.randFloat(0.008, 0.02),        // Terminal velocity
  wobbleSpeed: THREE.MathUtils.randFloat(0.001, 0.005), // Oscillation frequency
  wobbleAmount: THREE.MathUtils.randFloat(0.002, 0.01), // Oscillation amplitude
  wobbleOffset: Math.random() * Math.PI * 2             // Phase shift
};

Particle physics for snow

The key physics principles at work here:

  • Terminal velocity: Heavier snowflakes fall faster
  • Air resistance: Creates lateral movement
  • Random oscillation: Every flake follows a unique path
  • Phase offsets: Prevents unnatural synchronization

Sakura petal geometry

Creating realistic petal shapes requires translating different botanical forms to geometry:

// Creating a petal shape using bezier curves
const petalShape = new THREE.Shape();
// Start at the base
petalShape.moveTo(0, 0);
// Curve outward and upward to the tip
petalShape.bezierCurveTo(0.15, 0.1, 0.2, 0.15, 0.15, 0.3);
// Curve back down on the other side
petalShape.bezierCurveTo(0.1, 0.4, -0.1, 0.4, -0.15, 0.3);
// Complete the shape back to the base
petalShape.bezierCurveTo(-0.2, 0.15, -0.15, 0.1, 0, 0);

Sakura petal geometry

The bezier curve parameters aren't random. They're designed to mimic the slight asymmetry of natural petals:

  • The control points create the characteristic gentle curvature
  • The slight asymmetry makes the petals look organic
  • Using multiple petal shapes adds visual diversity

Wind dynamics

For the sakura petals, to simulate the interaction between petals and air:

// Sway motion combining multiple oscillations
petal.position.x += Math.sin(Date.now() * petal.userData.swaySpeed + 
                   petal.userData.swayOffset) * petal.userData.swayAmount;

// Slower oscillation on z-axis for depth
petal.position.z += Math.cos(Date.now() * petal.userData.swaySpeed * 0.7 + 
                   petal.userData.swayOffset) * petal.userData.swayAmount * 0.5;

// Multi-axis rotation simulates tumbling in the air
petal.rotation.x += petal.userData.rotationSpeedX;
petal.rotation.y += petal.userData.rotationSpeedY;
petal.rotation.z += petal.userData.rotationSpeedZ;

Wind dynamics: interaction between petals and air

This creates several realistic effects:

  • Larger movements occur at slower frequencies
  • Creates more unpredictable paths
  • Rotation affects how the petal catches the wind

Visual realism : material properties

For visual believability, materials are crucial (these could be enhanced by using right images):

// Snow with slight transparency
const snowMaterial = new THREE.MeshBasicMaterial({ 
  color: 0xe6f0ff,
  transparent: true, 
  opacity: 0.98
});

// Sakura petals with double-sided rendering
const petalMaterial = new THREE.MeshBasicMaterial({ 
  color: 0xff8fab,
  transparent: true, 
  opacity: 0.9,
  side: THREE.DoubleSide  
});

Visual realism: Transparent snow vs double-sided sakura petals

Performance optimizations

Three.js performance requires understanding the rendering pipeline:

// Instead of creating new particles, recycle existing ones
if (flake.position.y < -8) {
  flake.position.y = 18;  // Move back to the top
  flake.position.x = THREE.MathUtils.randFloatSpread(18);
  flake.position.z = THREE.MathUtils.randFloatSpread(18);
}

Three.js performance optimization

This recycling approach avoids:

  • Expensive garbage collection from destroying/creating objects
  • Memory fragmentation from constant allocation/deallocation
  • CPU spikes that would cause animation stutters

Beyond light / dark mode

This project reminded me why I fell in love with coding 2d / 3d graphics, game programming and visual systems years ago.

There's something deeply satisfying about building visual systems where code translates directly into motion. If you're looking for a break from your typical web development routine, I'd recommend playing with Three.js. It's accessible enough for quick experiments but deep enough for serious projects.

If you want to see it in action, check out exitcode42.com