Three.js Scroll Paper Plane Flight — Curve-Path Camera Follow

Three.js Scroll Paper Plane Flight · Scroll · Plain HTML, CSS & JS · Live preview

What's included

Features

Low-poly paper plane built from four flat triangles in a single BufferGeometry, no external 3D model required
THREE.CatmullRomCurve3 defines a smooth multi-waypoint climb-and-dive flight path sampled by even arc length
Plane orientation derived each frame via lookAt toward a point slightly ahead on the curve
Bank angle computed from the lateral difference between behind- and ahead-curve samples, no pre-authored keyframes
Chase camera samples the identical curve at an offset t value so it always trails the plane at a fixed arc-length distance
Static low-poly puff-cluster clouds placed once around the flight path for a cheap, stylized sky environment
DoubleSide material keeps thin plane triangles visible through extreme bank and roll angles
Fully reversible pinned scroll animation — scrolling up retraces the entire flight path and banking in reverse

About this UI Snippet

How to Build a Scroll-Driven Paper Plane Flight With Three.js

Screenshot of the Three.js Scroll Paper Plane Flight snippet rendered live

The Three.js Scroll Paper Plane Flight snippet flies a low-poly folded paper dart along a smooth 3D curve through a stylized sky of puffy low-poly clouds, with the camera trailing behind and the plane visibly banking into turns, all driven by one scrubbed scroll progress value.

A folded dart from four flat triangles, one BufferGeometry

Rather than importing a 3D-modeled airplane asset, the plane mesh is four flat triangles — describing the top and bottom of two wings meeting at a shared centerline fold — packed into a single THREE.BufferGeometry. side: THREE.DoubleSide on the material keeps every triangle visible from both sides as the plane rolls and banks through extreme angles, the same double-sided approach used for the flat panels in origami crane unfold, applied here to a rigid dart instead of a hinge-folding form.

CatmullRomCurve3 defines a smooth climb-and-dive path

A handful of waypoint Vector3s — climbing, banking sideways, diving, climbing again — are handed to THREE.CatmullRomCurve3, which interpolates a smooth spline through all of them. Calling curve.getPointAt(t) for any t between 0 and 1 returns a position an even arc-length fraction along that spline (not just parameter fraction), which is what keeps the plane's apparent speed visually consistent even through tighter and looser sections of the curve.

Orientation and banking derived from nearby curve samples

Every frame, the plane looks at a point slightly further along the curve (ahead) via lookAt, which orients its nose along the direction of travel. To bank into turns, the snippet also samples a point slightly *behind* the current position and compares the lateral (x-axis) difference between the behind-sample and the ahead-sample — a discrete estimate of how sharply the path is curving sideways at this point — and rolls the plane around its own forward axis proportionally with rotateZ. This produces a natural-looking bank into turns without needing any pre-authored animation keyframes.

Camera trails behind on the same curve, not on an independent path

Rather than defining a separate camera path, the camera samples the exact same curve at a slightly earlier t value (behindT = clamped - 0.045) so it always trails a fixed arc-length distance behind the plane, offset upward for a classic chase-cam framing, and looks directly at the plane's live position. Because both plane and camera derive their positions from the same single t parameter, they never desync, and the whole rig reverses cleanly.

Fully reversible flight

Every visual property — plane position, plane orientation, bank angle, and camera position — is recomputed fresh every frame purely from the current scrubbed t value sampled against the static curve. Scrolling back up decreases t, and the plane and camera both retrace the exact same climb-and-dive path in reverse, banking the opposite way through each turn exactly as it did going forward.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not need to reverse-engineer how a paper plane banks realistically into turns along a hand-designed 3D flight path. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to explain how getPointAt's even arc-length sampling keeps flight speed consistent, or how the behind/ahead curve-sample comparison produces a believable bank angle with no keyframes. The same assistant can help you extend it — ask it to add a paper-trail ribbon effect behind the plane, sync scroll-triggered text captions to specific t ranges along the curve, or add gentle procedural wind-sway to the clouds. 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:

text
Build a "scroll-scrubbed paper plane flight along a curved path" 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 the canvas element (not window.innerWidth/innerHeight) and updated on window resize including aspect ratio.
- Build a low-poly paper plane mesh from a small number of flat triangles in a single THREE.BufferGeometry (representing folded wing panels meeting at a centerline), with a DoubleSide material so thin panels stay visible through extreme rotation.
- Define a multi-waypoint flight path using THREE.CatmullRomCurve3 with several Vector3 points that climb, bank sideways, and dive to create visual variety, and populate the scene with a couple dozen static low-poly "cloud" objects (clusters of overlapping spheres) placed around the path.
- Register a GSAP tween on a ScrollTrigger targeting the pinned section, with pin: true, start at top top, a numeric scrub, and a multi-hundred-percent end, animating a single plain progress value from 0 to 1 with linear easing.
- Every animation frame, apply smoothstep easing to the scrubbed progress and clamp it into the curve's valid sampling range, then use curve.getPointAt to find the plane's current position and a point slightly further along the curve to orient the plane toward via lookAt.
- Compute a bank/roll angle by sampling the curve at a point slightly behind the plane's current position and comparing its lateral offset to the ahead-sample's lateral offset, then roll the plane around its own forward axis proportionally to that difference.
- Position the camera by sampling the same curve at a t value offset slightly behind the plane's own t, offset upward for a chase-cam angle, and have it look at the plane's live position every frame.
- Confirm scrolling back up reverses the entire flight smoothly: the plane and camera retrace the exact path and banking in reverse, since every value is derived fresh each frame from the current scrubbed progress with no accumulated state.

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.

Source Code

<section class="ppf-stage" id="ppfStage">
  <div class="ppf-intro-overlay"><p>Scroll ↓ to send the paper plane climbing and diving through the clouds</p></div>
  <canvas id="ppfCanvas"></canvas>
  <div class="ppf-hud"><span id="ppfPct">0</span>% of route flown</div>
</section>
<section class="ppf-bottom"><p>The paper plane completes its curved flight through the sky.</p></section>

Step by step

How to Use

  1. 1
    Load all three CDN scriptsAdd three.min.js, gsap.min.js, and ScrollTrigger.min.js from the CDN panel, in that order.
  2. 2
    Paste HTML, CSS, and JSA paper plane sits at the start of its route inside a bright sky, in a pinned 3D stage with a live "% of route flown" read-out.
  3. 3
    Scroll downThe plane climbs, banks, and dives along a smooth curved path through low-poly clouds while the camera trails behind it.
  4. 4
    Scroll back upThe plane and camera retrace the exact same path in reverse, banking the opposite way through each turn.
  5. 5
    Retune the routeEdit the CatmullRomCurve3 waypoint array to design a different climb-and-dive path or add more turns.
  6. 6
    Adjust the pacingChange the ScrollTrigger end value (+=500%) for a slower, more scenic flight or a faster one.

Real-world uses

Common Use Cases

Travel and airline landing pages
Open a travel-booking, airline, or trip-planning site with a playful paper plane journey through the hero section.
Product roadmap storytelling
Use each leg of the flight to represent a milestone, syncing headline copy to the plane's position along the curve.
Illustration and paper-craft portfolios
Showcase a playful, low-poly art direction with a piece that flies itself through a designed environment.
Kids' and educational app sites
A friendly paper plane flight suits children's learning apps or creativity-focused product pages.
Teaching curve-based camera rigs
A compact example of driving both an object and a chase camera from one shared spline parameter.
Casual flight or endless-runner game intros
Pair with origami crane unfold for a shared paper-craft visual language across a game's menu screens.

Got questions?

Frequently Asked Questions

Linearly interpolating between waypoints would produce a path with visible sharp corners at each waypoint. THREE.CatmullRomCurve3 fits a smooth spline through all the given points, and its getPointAt(t) method samples that spline by even arc-length fraction rather than raw parameter fraction, so the plane moves at a visually consistent speed even where waypoints are closer together or further apart.

Each frame, the snippet samples the curve at a point slightly behind and a point slightly ahead of the plane's current position, and compares their lateral (x-axis) difference. A larger difference means the path is curving more sharply sideways at that point, so the plane rolls proportionally further around its own forward axis via rotateZ — an approximation of how a real aircraft banks harder into a tighter turn, computed with no manual keyframing.

If the camera followed an independently authored path, keeping it visually locked behind the plane through every climb, dive, and turn would require constant manual tuning. Sampling the identical curve at a t value slightly earlier than the plane's own t guarantees the camera always trails at a consistent arc-length distance and banks through the same turns, since both derive from one shared parameter.

Yes. Plane position, orientation, bank angle, and camera position are all recomputed from scratch every frame purely as functions of the current scrubbed t value against the static curve — nothing is accumulated between frames. Scrolling up decreases t, and every computed value (including the bank direction) naturally retraces its forward-flight counterpart in reverse.

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 plane geometry, curve, clouds, and GSAP ScrollTrigger inside a mount effect keyed to a canvas ref, and on unmount kill the ScrollTrigger instance, dispose geometries/materials, and call renderer.dispose().