Three.js Scroll Constellation Web — GSAP Star Chart Reveal

Three.js Scroll Constellation Web · Scroll · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Star field generated once into a frozen Float32Array — no per-frame position updates
Edge candidates computed once at load with an O(n²) pass, filtered by distance, then sorted ascending
Reveal driven entirely by geometry.setDrawRange(0, count) — zero buffer rebuilds during scroll
Shrinking the draw range on scroll-up makes the reverse animation exactly as cheap as the forward one
Per-vertex pulseSize attribute on a secondary Points cloud drives short flash pulses on newly linked stars
Additive blending with depthWrite: false gives glowing neon crossings with no shader code
Ambient parallax rotation runs independently of scroll-driven reveal, so the scene never feels frozen
Capped edge count (220) keeps the constellation legible instead of collapsing into a solid mesh

About this UI Snippet

How to Build a Scroll-Revealed Constellation Web With Three.js and GSAP

Screenshot of the Three.js Scroll Constellation Web snippet rendered live

The Three.js Scroll Constellation Web snippet scatters a field of stars through 3D space and, as the visitor scrolls, progressively wires them together into glowing constellation lines — not with a particle system that fades in randomly, but with a precomputed, sorted list of edges whose reveal is driven directly by the scrollbar via a single scrubbed value.

Stars are generated once, positions frozen forever

At load time, 260 points are scattered inside a hollow sphere shell using spherical coordinates (theta/phi sampled from a uniform sphere distribution, radius randomized between 14 and 40 units) and written into a Float32Array backing a THREE.BufferGeometry. Those positions are never touched again. Freezing the star field up front is what lets the edge list computed in the next step stay valid for the lifetime of the scene — there is no need to recompute distances every frame because nothing moves relative to anything else, only the camera and the whole group rotate together.

A precomputed, sorted edge list instead of per-frame distance checks

Naively, you might loop over every star pair every frame and draw a line if they're close enough — that's an O(n²) distance check running 60 times a second for no reason, since the stars are static. Instead, the snippet loops over all pairs exactly once at startup, keeps only pairs closer than a threshold distance, and sorts the resulting candidate list by distance ascending. That sorted array becomes the fixed script for the whole scroll journey: edge 0 is the shortest link in the sky, edge 219 is the longest kept link, and scrolling simply decides how far into that script the reveal has progressed.

setDrawRange is the entire reveal mechanism

The line geometry is built once as a single THREE.BufferGeometry holding all edges as line-segment pairs, wrapped in one THREE.LineSegments object with additive blending for a glow-on-black look. Revealing more of the constellation as scroll progresses does not mean rebuilding that geometry, re-uploading a new attribute buffer, or adding new mesh objects — it means calling lineGeo.setDrawRange(0, revealedCount * 2) with a growing count. The GPU already has every possible edge in VRAM; the draw range just tells it how much of the array to actually rasterize this frame. That single call is why the reveal has effectively zero per-scroll-frame cost regardless of how many total edges exist, and why scrolling back up instantly un-reveals lines with no extra bookkeeping — shrinking the draw range is exactly as cheap as growing it.

Flashing a star the moment its first edge lands

To make each newly formed link feel like a discovery rather than a line silently appearing, the snippet tracks, for every star index touched by a newly revealed edge, a small expiry time in a flashUntil array. A second, parallel THREE.Points cloud sitting exactly on top of the star positions uses a per-vertex pulseSize attribute and additive blending to render as a soft cyan glow; each frame, any star whose flash window hasn't expired gets a temporary opacity boost proportional to how much of its 0.6-second flash window remains. This is a similar technique to the star-highlighting used in the network graph snippet, adapted here so it's driven by scroll position rather than user interaction.

Continuous rotation layered on top of discrete reveal

The reveal progress (t, scrubbed 0 to 1 across the pinned stage) and the ambient parallax rotation are deliberately kept independent: rotation advances every animation frame regardless of scroll state, while the edge count only changes when t changes. This separation means the constellation still feels alive — drifting gently, like the starfield warp backdrop — even when the visitor pauses mid-scroll with the scrollbar completely still, rather than freezing into a static image the moment scrolling stops.

Why LineBasicMaterial with additive blending, not a shader

A custom shader could draw fading gradient lines or animate line width, but for a webbing effect of thin glowing threads, THREE.LineBasicMaterial with blending: THREE.AdditiveBlending and depthWrite: false gets a convincing neon-on-black look for free: overlapping lines brighten where they cross, exactly like real light does, and there is no depth-fighting because nothing needs to write to the depth buffer for a wireframe web. This keeps the whole snippet dependency-free beyond three.js and GSAP, the same constraint followed by the holographic globe and comet trail snippets in this gallery.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to puzzle out how a fixed star field turns into an animated reveal on your own. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to explain why the edge list is sorted by distance before the scroll animation even begins, or why setDrawRange is preferable to adding and removing line objects. The same assistant can help extend the effect — ask it to color edges by a "constellation group" so the web resolves into named clusters, add drifting nebula-style background particles behind the stars, or trigger a camera dolly-in toward the most recently completed constellation. It can also help you profile and optimize, for example replacing the manual pulse-tracking loop with a small object pool if you push the star count much higher. Treat the code as a working draft to question and reshape, not a final answer.

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-revealed constellation web" 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 section containing a full-size canvas, with a WebGLRenderer and PerspectiveCamera sized to it and updated on window resize including aspect ratio.
- Scatter roughly 250-300 stars inside a hollow sphere volume using spherical coordinates, stored once in a Float32Array-backed BufferGeometry rendered as THREE.Points, and never mutated afterward.
- At load time, compute all star pairs within a fixed distance threshold, sort the resulting list ascending by distance, and cap it at a reasonable maximum (e.g. 200-250 edges) to keep the constellation legible.
- Build one THREE.BufferGeometry holding every edge as a line-segment vertex pair, rendered via a single THREE.LineSegments with additive blending and depthWrite disabled, starting with geometry.setDrawRange(0, 0).
- 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 t from 0 to 1.
- Every animation frame (requestAnimationFrame, independent of the scroll callback), map t to a target edge count and call geometry.setDrawRange(0, targetCount * 2) only when the count changes — never rebuild the geometry or its attributes during scroll.
- When new edges are revealed, briefly flash their two endpoint stars using a secondary Points cloud with a per-vertex size/opacity attribute that decays over a fraction of a second.
- Apply a slow continuous rotation to the whole scene, independent of the scroll-driven reveal, for ambient parallax.
- Confirm scrolling back up shrinks the draw range and instantly un-reveals edges, with no extra bookkeeping beyond the same setDrawRange call.

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 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 dark star field renders in a pinned 3D stage with a live link-count HUD.
  3. 3
    Scroll downConstellation lines connect nearby stars one by one, each new link briefly flashing its endpoints.
  4. 4
    Scroll back upThe web un-draws itself instantly, since revealedCount and setDrawRange track scroll position directly.
  5. 5
    Retune the web densityChange the distance threshold (7.5) in the candidate-pair loop or the edges.slice cap (220) to make a sparser or denser sky.
  6. 6
    Tune the reveal lengthChange the ScrollTrigger end value (+=450%) for a slower or faster constellation reveal.

Real-world uses

Common Use Cases

Data-story landing pages
Frame a network, team, or knowledge-graph story as stars connecting into a constellation as visitors scroll through the narrative.
Astronomy and stargazing sites
A literal star-chart reveal fits planetarium promos, astronomy apps, and night-sky education content.
Teaching draw-range optimization
A compact, real example of geometry.setDrawRange as a near-zero-cost reveal technique, contrasted with rebuilding buffers every frame.
Portfolio "connections" sections
Use the web as a visual metaphor for skills, collaborators, or projects linking together, similar in spirit to the network graph snippet but scroll-paced.
Brand or product launch reveals
Pace a slow build-up toward a climactic fully-connected web timed to coincide with a hero statement further down the page.
Lore and world-building pages
Chart fictional star systems or faction relationships that assemble as the player scrolls through backstory.

Got questions?

Frequently Asked Questions

The stars never move relative to each other, so their pairwise distances never change after the scene loads. Running the O(n²) distance check once at startup and caching a sorted, capped edge list means the animation loop does zero geometry math for connectivity — it only reads a cursor position into a static array, which is dramatically cheaper than re-evaluating every pair 60 times a second.

Adding and removing THREE.Object3D instances triggers scene-graph updates and extra draw calls, and rebuilding a BufferGeometry re-uploads its attributes to the GPU. setDrawRange keeps one BufferGeometry with every possible edge already resident in VRAM and simply tells the GPU how many vertices to rasterize this frame, so revealing (or hiding) hundreds of edges is a single cheap integer write with no allocation or re-upload.

The main star field uses one PointsMaterial shared by all 260 points, so its size and opacity apply uniformly and cannot spotlight a single star. A second Points object sharing the same positions but carrying a per-vertex pulseSize attribute and additive blending can be driven independently, letting individual stars flash without touching or duplicating the base star field.

No — the heaviest work (the O(n²) candidate search) runs once during setup with only 260 stars, and every scroll-frame update is O(1) beyond a small loop over active flashes. The GPU cost is a single Points draw call and a single LineSegments draw call with a bounded vertex count, well within budget even on integrated graphics and mid-range mobile GPUs.

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 star field, edge list, and ScrollTrigger tween inside a mount effect against a canvas ref, and on cleanup kill the ScrollTrigger instance (or revert a gsap.context), dispose the geometries and materials, and call renderer.dispose() so nothing leaks when the component unmounts.