Three.js Scroll Neural Network Pulse — Layered Graph Assembly

Three.js Scroll Neural Network Pulse · Scroll · Plain HTML, CSS & JS · Live preview

What's included

Features

All nodes render through a single THREE.InstancedMesh regardless of LAYER_SIZES configuration
Connections are generated once as fully-connected index pairs between adjacent layers, no hardcoded topology
A single LineSegments BufferGeometry re-reads live node positions every frame, keeping links perfectly in sync with nodes
Node assembly is a pure lerp between precomputed scatter and target positions, guaranteeing full reversibility
Forty reusable pulse markers travel along random links via a fixed-size object pool with zero per-frame allocation
Pulses remap scroll progress into a visibility gate so signals only appear once the network is mostly wired
Connection line opacity ramps with scroll progress as a second visual channel alongside node assembly
Fully reversible pinned scroll animation — scrolling up scatters nodes and fades pulses/links in reverse

About this UI Snippet

How to Build a Scroll-Driven Neural Network Pulse With Three.js

Screenshot of the Three.js Scroll Neural Network Pulse snippet rendered live

The Three.js Scroll Neural Network Pulse snippet represents a small neural network as instanced node spheres, a single LineSegments web of connections, and a pool of traveling pulse markers. As the visitor scrolls through a pinned stage, scattered nodes assemble into clean layers, their connections fade in, and once mostly formed, glowing pulses begin flowing from the input layer toward the output layer.

Nodes as data, not as individually authored meshes

Every node is a plain JS object carrying a scatter position (its chaotic starting point), a target position (its slot in a layered grid derived from LAYER_SIZES and LAYER_X), and a live position updated every frame. All nodes share one THREE.InstancedMesh, so however many layers or nodes-per-layer the network has, node rendering stays a single draw call — the same instancing discipline used for backbone spheres in DNA helix unwind, applied here to a graph layout instead of a helix.

Fully connected layers via precomputed index pairs

Connections are generated once at startup: every node in layer L is paired with every node in layer L+1, and each pair is stored as a two-element index array into the flat nodes list. This produces a classic fully-connected feed-forward topology without hardcoding any specific node count. All links share one THREE.BufferGeometry rendered via THREE.LineSegments, with endpoint positions rewritten into a flat Float32Array every frame by reading each linked node's live position — meaning the connections automatically track the nodes as they assemble, with zero extra bookkeeping.

Assembly driven by one lerp per node

Each node's live position is simply lerp(scatter, target, eased), where eased is the smoothstepped scroll progress. Because both endpoints are fixed and precomputed, scrolling back up drives every node — and by extension every connection line reading from those nodes — back toward its scattered starting position in perfect reverse, with no separate "disassemble" logic required.

Pulses as a reused object pool, gated by progress

Forty pulse markers share their own THREE.InstancedMesh and are never created or destroyed at runtime. Each pulse tracks which link it is traveling along and a 0-1 progress value that increments every frame and wraps to a newly chosen random link when it reaches 1. Pulses only render at meaningful scale once eased passes roughly 0.55, remapped into a 0-1 pulseVisible value — so pulses fade in only after the network has mostly assembled, reinforcing the sense that signals can't flow through a network that hasn't finished wiring itself yet.

Line opacity as a second progress-driven channel

On top of positions, the connecting lines' opacity itself ramps from a faint 0.06 to a much more visible 0.4 as eased increases, so the web of connections visually strengthens in tandem with the nodes snapping into their layered grid, rather than being either fully invisible or fully opaque at every point in the scroll.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not need to reverse-engineer how a scattered point cloud assembles into a wired, pulsing neural network diagram. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to explain how the fully-connected index-pair generation works, or why pulses use a fixed-size reused object pool instead of dynamic allocation. The same assistant can help you extend it — ask it to color-code pulses by which layer transition they're crossing, add a subtle activation "glow" on each node when a pulse arrives, or vary link opacity by simulated connection weight. 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 neural network assembly and signal pulse" effect 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.
- Define a network topology as an array of layer sizes (e.g. [5, 8, 8, 4]) and assign each node a target position in a layered grid (x by layer index, y spread evenly within the layer) plus a random scattered starting position.
- Render all nodes through a single THREE.InstancedMesh regardless of node count, using a reused dummy Object3D and setMatrixAt to write each instance's live transform every frame.
- Generate a fully-connected set of links once at startup by pairing every node in each layer with every node in the next layer, stored as index pairs into the flat node list.
- Render all connections through one THREE.LineSegments backed by a single BufferGeometry, rewriting its position attribute every frame by reading each link's two endpoint nodes' current live positions.
- 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, lerp every node's live position between its scattered and target position by that eased value, and ramp the connection line material's opacity with the same eased value.
- Implement a fixed-size pool (e.g. 40) of pulse markers, also rendered via one InstancedMesh, each tracking a current link and a 0-1 travel progress that increments every frame and reassigns to a new random link when it completes a traversal; remap the eased scroll progress above some threshold into a visibility factor that scales pulse instances toward zero until the network is mostly assembled.
- Confirm scrolling back up reverses the entire effect smoothly: pulses fade out, connection opacity drops, and nodes scatter back toward their random starting positions, since every visual property is a direct function of the shared progress value 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="nnp-stage" id="nnpStage">
  <div class="nnp-intro-overlay"><p>Scroll ↓ to assemble the network and fire signal pulses</p></div>
  <canvas id="nnpCanvas"></canvas>
  <div class="nnp-hud"><span id="nnpPct">0</span>% connected</div>
</section>
<section class="nnp-bottom"><p>A fully wired network, pulsing signals layer to layer.</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 scattered cloud of nodes appears inside a pinned 3D stage with a live "% connected" read-out.
  3. 3
    Scroll downNodes assemble into layered columns, faint connections strengthen, and glowing pulses begin flowing once mostly formed.
  4. 4
    Scroll back upPulses fade, connections dim, and nodes scatter back to their random starting positions exactly in reverse.
  5. 5
    Retune the topologyEdit LAYER_SIZES to add or remove layers and nodes per layer; connections regenerate automatically to stay fully connected.
  6. 6
    Adjust the pacingChange the ScrollTrigger end value (+=450%) or the 0.55 pulse-visibility threshold to retime the reveal.

Real-world uses

Common Use Cases

Machine learning and AI education
Open a deep-learning course or AI-product explainer page with a network that visibly assembles and fires signals.
AI/ML SaaS landing pages
A neural network reveal suits AI infrastructure, MLOps, or model-hosting product hero sections.
Data platform dashboards
Use a static formed-state variant as an ambient background for analytics or graph-database product pages.
Conference and hackathon microsites
Pair with scroll number odometer stats for an AI-themed event page.
Teaching graph data structures
A compact example of representing a fully-connected layered graph with InstancedMesh and LineSegments.
Scroll-story chapter breaks
Use the assembly as a mid-page transition, similar in spirit to the scroll tunnel bridge.

Got questions?

Frequently Asked Questions

Links are stored as index pairs into the flat nodes array, never as fixed positions. Every frame, the LineSegments position buffer is rewritten by reading each linked node's current live position, so connections always track wherever their endpoint nodes currently are, whether scattered, mid-assembly, or fully formed.

Creating and destroying objects (and their geometries or mesh entries) every frame causes garbage collection pauses and complicates InstancedMesh bookkeeping. Instead, all 40 pulses exist for the lifetime of the scene; each simply gets reassigned to a new random link and resets its progress to 0 whenever it finishes traveling the current one, so the total instance count never changes.

Signals traveling through an unformed, scattered network would look like errant noise rather than a legible pulse effect. Remapping eased progress above a threshold into a 0-1 pulseVisible value (and scaling pulse instances toward zero below it) means pulses only become visually meaningful once the layered structure has mostly assembled, matching the metaphor of "signal flowing through a wired network."

Yes. Node positions are a pure lerp between two fixed, precomputed points driven by one progress value, connection lines are derived from those same live node positions every frame, and pulse visibility and line opacity are both direct functions of the same progress value. Scrolling up drives progress back toward zero and every visual layer unwinds in step, with no separate reverse-animation code needed.

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 node/link data, InstancedMesh and LineSegments objects, 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().