Three.js Scroll Ocean Dive — GSAP Depth Color Grade

Three.js Scroll Ocean Dive · Scroll · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Two-stage depth color grade: sky→shallow across the plunge, shallow→abyss below, split at depth 0.25
Fog color copied from the background lerp each frame so distant geometry dissolves into water
Double-sided displaced water surface you can dive through and look back up at from below
Recycled 350-point bubble field rising against the descent for doubled perceived speed
God rays from seven additive-blended planes, opacity = sine shimmer × (1 − depth)
Directional light intensity tied to depth so the sea floor is lit by fog color, not sun
Sea floor dunes from a sine-displaced plane plus 24 randomly rotated dodecahedron rocks
Single scrubbed camera-Y value — resurfacing reverses the entire grade with no one-shot triggers

About this UI Snippet

How to Build a Scroll-Driven Ocean Dive With Three.js and GSAP

Screenshot of the Three.js Scroll Ocean Dive snippet rendered live

The Three.js Scroll Ocean Dive snippet sinks the camera from just above an animated water surface down to a rocky sea floor 130 meters below, using GSAP's ScrollTrigger to scrub a single camera-Y value while a per-frame depth fraction drives fog color, light intensity, bubble visibility, and volumetric god rays. Where most scroll scenes travel forward along Z like the scroll tunnel, this one is a vertical journey — and almost all of its storytelling is done with color.

A two-stage color grade instead of one lerp

Water does not darken linearly: the plunge from air to water is an abrupt hue shift, then the fade to black is long and slow. The frame loop converts camera Y into a 0–1 depth fraction and splits it at 0.25 — the first quarter lerps the background and fog from sky blue to shallow teal, the remaining three quarters lerp from teal to near-black abyss. Both scene.background and scene.fog.color are copied from the same lerped color each frame, which is the critical trick: when fog matches the background exactly, distant geometry dissolves into "water" rather than fading against a mismatched backdrop.

The surface is a displaced plane you dive through

The water surface is a 64×64-segment plane whose vertices are re-displaced every frame with two offset sine waves, exactly like the height-field technique in the wave terrain snippet but rendered with side: THREE.DoubleSide at 85% opacity. That double-sided material matters: after the camera passes through it, the user looks up at the underside of the waves — a view a single-sided plane would simply cull away.

Bubbles rise while the camera sinks

Three hundred fifty points drift upward a fixed amount per frame and wrap back to the bottom once they cross the surface, following the recycle-not-respawn pattern from the starfield warp. Because the camera moves down while the bubbles move up, the relative velocity doubles the perceived descent speed at zero extra cost. Their material opacity snaps to zero while the camera is still above the waterline so no bubbles float in the sky.

God rays from additive planes, faded by depth

Seven tall planes with AdditiveBlending, near-zero opacity, and random Y rotations stand in for volumetric light shafts. Each ray's opacity is the product of a slow sine shimmer and (1 − depth), so the shafts wave gently in the shallows and die out completely before the abyss — matching the physical fact that sunlight does not reach deep water. The directional light's intensity is scaled by the same factor, dimming the sea floor until the fog color provides most of what you see.

One scrubbed value, everything else derived

The GSAP tween scrubs only dive.y from +14 to −116 with ease: 'none'. Depth fraction, both color lerps, fog density, sun intensity, bubble opacity, ray fade, and the live DEPTH HUD all derive from that value inside requestAnimationFrame, so scrolling back up resurfaces through the same color grade in reverse, with no one-shot triggers to desynchronize. Surface waves, camera sway, and bubble wobble run on THREE.Clock time so the ocean keeps moving while the scroll is idle — the same scrubbed-primary, clock-secondary split used across this series, including the planet approach.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not need to work out the underwater color science yourself. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to explain the two-stage fog grade, why fog color must equal background color, or how the god rays fake volumetric light with additive planes. The same assistant can extend the dive — adding a school of fish as an instanced mesh weaving past the camera, feature-callout HTML pinned at specific depths, a pressure gauge that accelerates with depth, or caustic light patterns on the sea floor from an animated texture. It can also retheme the whole scene to a brand palette by regenerating the three grade colors. Treat the code as a starting point to question and reshape, not a finished artifact.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "scroll-scrubbed ocean dive" in plain HTML, CSS, and JavaScript using Three.js and GSAP's ScrollTrigger plugin, all loaded from a CDN (no bundler, no build step).

Requirements:
- A pinned full-viewport section with a canvas, WebGLRenderer, and PerspectiveCamera resized (with aspect) on window resize.
- A water surface from a 64×64-segment PlaneGeometry rotated flat, displaced every frame by two offset sine waves, rendered with a DoubleSide MeshLambertMaterial at ~85% opacity so the camera can pass through and look back up at it.
- One GSAP tween (ease "none") scrubbing camera Y from ~+14 to ~−116 on a ScrollTrigger with pin: true and end around +=500%.
- Each frame derive a 0–1 depth fraction from camera Y and run a TWO-STAGE color grade: lerp sky→shallow-teal across the first quarter, teal→near-black across the rest, copying the result into BOTH scene.background and scene.fog.color, while also raising fog density and dimming a DirectionalLight by depth.
- 300+ bubble points that rise a fixed amount per frame and wrap from the surface back to the bottom by mutating one Float32Array in place; hide them (opacity 0) while the camera is above the waterline.
- 5–8 god-ray planes: tall PlaneGeometry with AdditiveBlending, depthWrite false, opacity ~0.06, random Y rotation, each fading by (1 − depth) times a slow sine shimmer.
- A sea floor at the bottom: a plane displaced into dunes with sin/cos, computeVertexNormals called once, plus ~24 randomly rotated DodecahedronGeometry rocks.
- A live "DEPTH n M" HUD derived from camera Y, an intro overlay that fades once the dive starts, and clock-driven camera sway so the scene moves while scroll is idle.
- Confirm scrolling back up resurfaces through the same color grade in reverse — every visual state must derive from the scrubbed Y and the clock, with no one-shot events.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Load the three CDN scriptsAdd three.min.js, gsap.min.js, and ScrollTrigger.min.js in that order before the snippet JS.
  2. 2
    Paste HTML, CSS, and JSA pinned view hovers above animated waves under a sky-blue background, with a DEPTH 0 M HUD in the corner.
  3. 3
    Scroll to plungeThe camera passes through the double-sided surface — background and fog snap through the sky-to-teal stage of the color grade.
  4. 4
    Keep scrolling into the deepGod rays shimmer and fade, bubbles stream upward past the camera, light dims, and the teal-to-abyss lerp takes over.
  5. 5
    Reach the sea floorDisplaced dunes and scattered dodecahedron rocks emerge from the fog at around 130 m as the scrub completes.
  6. 6
    Retune the gradeChange the SKY, SHALLOW, and ABYSS colors or the 0.25 stage split to restyle the whole dive — every fade derives from those three constants.

Real-world uses

Common Use Cases

Ocean, diving, and marine-conservation sites
Scroll depth maps naturally to real dive depth — annotate the descent with facts at each stage using a scroll year timeline pattern alongside.
Product reveals with a "deep dive" metaphor
Land the scrub on a product resting on the sea floor for analytics or research tools that market themselves as going deeper.
Portfolio section transitions
Use the abyss endpoint as a natural handoff into a dark-themed section, the inverse of a scroll zoom hero opening.
Teaching fog-based scene design
A focused example of why matching FogExp2 color to scene.background is the highest-leverage trick in atmospheric Three.js scenes.
Underwater game and app promos
Pair the descent with feature callouts that surface at set depths, then hand off to a scroll camera path for a floor-level tour.
Ambient scrollytelling art pages
The clock-driven waves and bubbles keep the scene alive between scrolls, similar to the idle motion in galaxy formation.

Got questions?

Frequently Asked Questions

A single lerp spends most of the scroll in muddy mid-tones and makes the water entry feel gradual, which reads as fog rather than a plunge. Splitting at depth fraction 0.25 gives an abrupt sky-to-teal shift across the surface crossing, then a long teal-to-black fade below — matching how light actually behaves in water and making the moment you submerge unmistakable.

FogExp2 blends geometry toward the fog color with distance. If that color matches the background, distant objects dissolve seamlessly into "water" and the scene reads as a continuous volume. If the two colors differ even slightly, every mesh gets a visible halo where fogged pixels meet the backdrop, instantly breaking the underwater illusion. Both are copied from the same lerped THREE.Color each frame.

Seven tall PlaneGeometry meshes with AdditiveBlending, depthWrite: false, and opacity around 0.06 are fanned at random rotations below the surface. Additive blending means overlapping rays brighten each other like real light shafts, and each ray's opacity is multiplied by (1 − depth) plus a slow sine so they shimmer in the shallows and vanish before the abyss. It is a fake, but at these opacities the eye accepts it completely.

Relative motion is what the eye measures. Bubbles rising at a fixed rate while the camera sinks doubles the perceived descent speed without shortening the scroll distance or increasing scrub speed. They wrap from surface back to −120 in place — one Float32Array updated per frame, one draw call, no allocation — the same recycling used by the starfield warp snippet.

Yes. Export via the JSX, Vue, Angular, or Tailwind buttons. Mount the scene in useEffect / onMounted / ngAfterViewInit against a canvas ref, and on teardown kill the ScrollTrigger, dispose the surface, floor, bubble, and ray geometries and materials, and call renderer.dispose(). The surface-wave base positions are captured with slice() at build time, so keep that copy inside the effect scope.