More Scroll Snippets
Three.js Scroll Tunnel — GSAP ScrollTrigger WebGL Flythrough
Three.js Scroll Tunnel Travel · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
How to Build a Scroll-Driven Neon Tunnel Flythrough With Three.js and GSAP

The Three.js Scroll Tunnel snippet drops the visitor inside a glowing wireframe tube and flies them down its length as they scroll — the scrollbar drives camera position directly along a curved 3D path, not a timer — by pairing Three.js TubeGeometry with GSAP's ScrollTrigger plugin, both loaded from a CDN.
One curve defines both the tunnel and the camera path
The whole effect hinges on a single CatmullRomCurve3 built from a dozen control points that gently wander in X and Y while marching steadily in negative Z. That one curve is used twice: once to extrude the TubeGeometry that becomes the visible tunnel walls, and again as the exact path the camera travels. Because the geometry and the camera share the same curve, the camera is always perfectly centered inside the tube looking down its throat — there is no separate "align the camera to the wall" step to get wrong.
A single scrubbed number walks the whole scene
Rather than tweening the camera object, the snippet scrubs one plain value — travel.t — from 0 to just under 1. Every frame, curve.getPointAt(t) returns the camera's exact world position and curve.getPointAt(t + 0.01) returns a look-ahead point a little further down the tube. Deriving everything from one normalized parameter means the tunnel can be reshaped, lengthened, or curved differently without touching the ScrollTrigger timeline at all.
Look-ahead, not look-at-center
If the camera simply stared at the tunnel's far end, cornering would feel wrong — you would see the wall swing past rather than the path bend toward you. Instead the look target is sampled a small fraction of the curve *ahead* of the camera's current position, so the camera always faces the direction of travel and banks naturally into each turn, exactly like a real vehicle following a track.
Neon rings sell the speed
Forty torus rings are threaded along the curve at even intervals, each oriented perpendicular to the path using curve.getTangentAt() and lookAt(), and tinted across a hue sweep. A featureless tube gives almost no sense of motion; the rings streaking past the camera are what make the flythrough read as genuine forward speed, and they double as depth markers.
Exponential fog hides the seams
FogExp2 fades the far end of the tunnel into the background color, which both hides where the finite tube geometry ends and concentrates the neon glow near the camera. Because the fog color matches the page background exactly, the WebGL canvas blends seamlessly into the surrounding scroll sections above and below the pinned stage.
scrub: 0.7 for cinematic glide
A numeric scrub value lets the camera position glide toward the scroll position over about seven-tenths of a second rather than snapping frame-perfectly. Fast camera travel through a tunnel amplifies input jitter, so this smoothing is what turns raw trackpad and wheel noise into a fluid, deliberate ride. This is the same scrubbing pattern used by the scroll camera path snippet, applied to a curved tube instead of a straight gallery. Pair it with a starfield warp intro or a synthwave terrain outro for a full retro-futurist scroll journey.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You do not need to reverse-engineer how a single scrubbed number can drive an entire curved flythrough. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to walk through why the tunnel geometry and the camera share one CatmullRomCurve3, or why the look target is sampled ahead of the camera rather than at a fixed point. The same assistant can help you extend it — ask it to add a subtle roll that leans into each turn using the curve's tangent, spawn particles that stream backward past the camera for extra speed, or trigger a color pulse on the rings in time with an audio track. It can also optimize the scene, for instance merging the ring meshes into a single instanced mesh so forty draw calls collapse into one. Treat the code as a conversation starter, 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:
Build a "scroll-scrubbed neon tunnel flythrough" in plain HTML, CSS, and JavaScript using Three.js, GSAP, and GSAP's ScrollTrigger plugin, all loaded from a CDN (no bundler, no build step).
Requirements:
- A pinned section containing a full-size canvas, with a WebGLRenderer and PerspectiveCamera sized to it and updated on window resize including aspect ratio.
- Build a single THREE.CatmullRomCurve3 from about a dozen control points that wander gently in X and Y while stepping steadily in negative Z.
- Extrude a THREE.TubeGeometry along that curve and render it as a wireframe (LineSegments over WireframeGeometry) so the tunnel walls glow.
- Thread roughly 40 torus rings along the same curve at even intervals, each positioned with getPointAt and oriented perpendicular to the path using getTangentAt and lookAt, tinted across a hue sweep.
- Add THREE.FogExp2 whose color matches the page background so the far end of the tube fades out and the canvas blends into surrounding scroll sections.
- Register a GSAP tween on a ScrollTrigger targeting the pinned section, with pin: true, start at top top, a numeric scrub around 0.7, and an end several hundred percent tall, animating a single plain value t from 0 to just under 1.
- Every animation frame (requestAnimationFrame, independent of the scroll callback), set the camera position to curve.getPointAt(t) and call camera.lookAt on curve.getPointAt(t + a small delta) so the camera always faces the direction of travel and banks into turns.
- Confirm scrolling back up reverses the entire flythrough, since t is fully scrubbed rather than a one-way timer.Step by step
How to Use
- 1Load all three CDN scriptsAdd three.min.js, gsap.min.js, and ScrollTrigger.min.js from the CDN panel, in that order.
- 2Paste HTML, CSS, and JSA neon wireframe tunnel appears in a pinned 3D stage with a live depth read-out.
- 3Scroll downThe camera flies down the curved tube, passing through glowing rings tied directly to scroll position.
- 4Scroll back upThe flythrough reverses exactly, since the travel value is fully scrubbed rather than timer-based.
- 5Reshape the tunnelEdit the control-point loop to change how the curve wanders, or the TubeGeometry radius to widen it.
- 6Tune the ride lengthChange the ScrollTrigger end value (+=500%) for a longer, slower descent or a shorter, quicker one.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
The TubeGeometry is extruded along a CatmullRomCurve3, and the camera travels that identical curve via getPointAt. Sharing one curve guarantees the camera is always centered inside the tube looking down its length, so there is never a mismatch between where the walls are and where the camera points — reshaping the curve reshapes both at once.
Facing a fixed far point makes corners feel like the wall is swinging past you. Sampling the curve a small fraction ahead of the camera's current position (t + 0.01) means the camera always aims down the direction of travel, so it banks naturally into each bend the way a vehicle follows a track.
Forty torus rings are placed along the curve at even intervals, each oriented perpendicular to the path with getTangentAt and lookAt. A bare tube gives almost no motion cue; the rings streaking past the camera are what read as real forward velocity, and they act as evenly spaced depth markers.
FogExp2 fades the distant tube into the page background color. Matching the fog to the CSS background hides where the finite geometry ends and lets the WebGL canvas blend seamlessly into the plain scroll sections above and below the pinned stage, so there is no visible edge to the 3D scene.
Yes. Click JSX for a React component, Vue for a Vue 3 SFC, Angular for a standalone component, or Tailwind for a React + Tailwind version. Build the curve, geometry, and GSAP timeline inside a mount effect against a canvas ref, and on cleanup kill the ScrollTrigger instance (or revert a gsap.context) and call renderer.dispose() so the pin and WebGL context are released on unmount.