Three.js Scroll DNA Helix Unwind — 3D Double Helix Animation

Three.js Scroll DNA Helix Unwind · Scroll · Plain HTML, CSS & JS · Live preview

What's included

Features

Three THREE.InstancedMesh objects (strand A, strand B, rungs) render the entire helix in three draw calls
Per-base-pair state reduced to two scalars (angle, height); positions derived via cos/sin keep both strands locked in a true helix
A single reusable cylinder geometry is rescaled and reoriented per frame via lookAt to represent every rung
Smoothstep easing decouples the scrubbed linear scroll progress from the physical unwind motion
Camera orbits continuously with radius and height tied to progress for a dynamic but readable final shot
Zero per-frame allocations: dummy Object3D and Vector3 helpers are created once and reused every frame
Fully reversible pinned scroll animation — scrolling up re-twists and re-compresses the strand exactly in reverse
Two-tone emissive materials (teal / magenta) plus a warm rung color read clearly against a dark background

About this UI Snippet

How to Build a Scroll-Driven DNA Helix Unwind With Three.js

Screenshot of the Three.js Scroll DNA Helix Unwind snippet rendered live

The Three.js Scroll DNA Helix Unwind snippet represents a double helix as two strands of instanced spheres plus a set of instanced connecting rungs, all packed into three THREE.InstancedMesh objects. As the visitor scrolls through a pinned stage, every base pair's twist angle and vertical position lerp from a tightly compressed, over-twisted starting state to a loosely wound, fully extended strand.

Three InstancedMesh objects, not hundreds of separate meshes

Rendering seventy base pairs the naive way would mean 210+ individual meshes (two backbone spheres and one rung per pair), each with its own draw call. Instead this snippet allocates one THREE.InstancedMesh for strand A's spheres, one for strand B's spheres, and one for the rungs, each sized to N. A single reusable THREE.Object3D dummy computes each instance's transform and writes it into the mesh's instance matrix via setMatrixAt, so the whole helix renders in three draw calls regardless of how many base pairs it has. This is the same instancing discipline used in the particle work of galaxy formation, applied here to discrete rigid-body instances instead of points.

Angle and height as the only two interpolated quantities

Rather than storing full 3D start/end positions per point, each base pair only needs two scalars: a twist angle and a height. Strand A's position is derived from the angle via cos/sin * RADIUS, and strand B is always the mirrored point at angle + PI, which keeps the two backbones geometrically locked into a true double helix at every frame instead of drifting apart. Precomputing angleA0/angleA1 and y0/y1 once up front means the render loop only performs a lerp and a couple of trig calls per base pair — cheap enough for 60fps even before instancing is considered.

Rungs oriented per frame, not pre-modeled

The connecting rung between each base pair is a single unit-length cylinder geometry, translated so its origin sits at one end. Every frame, the dummy object is scaled along Z to the live distance between strand A and strand B and pointed at strand B with lookAt, so one shared cylinder geometry can represent a rung of any length or orientation without ever needing new geometry — the same trick used for connector lines in the neural network pulse snippet.

Smoothstep easing and an orbiting camera

A single scrubbed progress value form.t is smoothstepped into eased before driving any interpolation, giving the unwind an accelerate-and-settle feel rather than constant-speed motion. The camera continuously orbits the strand at a slowly increasing angle, and its radius and height both shift with eased so the final extended helix is framed from a slightly wider, more centered shot than the compressed starting coil.

Fully reversible by construction

Because every base pair's angle and height are lerped between two fixed, precomputed endpoints, scrolling back up simply drives eased back toward zero and every sphere and rung retraces its exact path — there is no accumulated or one-way state anywhere in the animation, matching the reversibility guarantee also used in particle assembly.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not need to reverse-engineer how a double helix unwinds with only a handful of draw calls. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to explain why the rungs are reoriented with lookAt every frame instead of being pre-modeled, or how the angle/height parameterization keeps both strands geometrically locked. The same assistant can help you extend it — ask it to add a third strand for an RNA-style ribbon, color-code base pairs by type, or add a subtle pulse of light traveling along one strand once fully unwound. 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 DNA double helix unwind" 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.
- Represent roughly 70 base pairs using three THREE.InstancedMesh objects: one for strand A's backbone spheres, one for strand B's backbone spheres, and one for the connecting rungs, so the whole helix renders in three draw calls total.
- For each base pair, precompute a start twist angle and height (tightly twisted, vertically compressed) and an end twist angle and height (loosely twisted, vertically extended) as parallel Float32Arrays.
- Derive strand A's live position each frame from cos(angle)*radius / sin(angle)*radius and strand B's from the same angle plus PI, so the two backbones stay a true double helix at every interpolated state.
- Reuse one THREE.Object3D "dummy" helper to compute and write each instance's matrix via setMatrixAt every frame, and set instanceMatrix.needsUpdate = true after updating all instances of a mesh.
- Orient each rung's cylinder by scaling it to the live distance between its two strand points and calling lookAt, so one shared cylinder geometry represents every rung regardless of length.
- 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, then lerp every base pair's angle and height between its start and end values by that eased amount, and slowly orbit the camera around the helix with radius/height tied to the same eased value.
- Confirm scrolling back up reverses the entire unwind smoothly, since every base pair lerps between two fixed, precomputed states.

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="dhx-stage" id="dhxStage">
  <div class="dhx-intro-overlay"><p>Scroll ↓ to unwind the compressed double helix</p></div>
  <canvas id="dhxCanvas"></canvas>
  <div class="dhx-hud"><span id="dhxPct">0</span>% unwound</div>
</section>
<section class="dhx-bottom"><p>A fully extended, readable double helix.</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 compressed, tightly twisted double helix appears inside a pinned 3D stage with a live "% unwound" read-out.
  3. 3
    Scroll downThe helix untwists and stretches vertically, base pair by base pair, while the camera slowly orbits.
  4. 4
    Scroll back upThe strand re-compresses and re-twists exactly in reverse, since every point lerps between two fixed states.
  5. 5
    Retune the shapeChange N for more or fewer base pairs, or TWIST_START/TWIST_END and HEIGHT_START/HEIGHT_END for a tighter coil or longer strand.
  6. 6
    Adjust the pacingChange the ScrollTrigger end value (+=450%) to slow down or speed up the unwind relative to scroll distance.

Real-world uses

Common Use Cases

Biology and genetics education
Open a genomics or biotech course page with a helix that visibly untwists to reveal its base-pair structure.
Biotech and health-tech landing pages
A scroll-driven DNA reveal suits gene-therapy, diagnostics, or pharma company hero sections.
Science museum and exhibit sites
Pair with scroll text clip reveal headlines describing each stage of the unwind.
Generative/data-art portfolios
Demonstrate instanced-mesh technique with a piece that visibly shows its own underlying data structure.
Scroll-story chapter breaks
Use the unwind as a mid-page transition, similar in spirit to the scroll tunnel bridge.
Teaching InstancedMesh performance
A compact real-world example of representing many rigid bodies with a handful of draw calls.

Got questions?

Frequently Asked Questions

Seventy base pairs need 210+ individual shapes; rendering each as its own mesh means hundreds of draw calls and matrix updates per frame. THREE.InstancedMesh lets all spheres of one strand share one geometry, one material, and one draw call, with per-instance transforms written into a shared matrix buffer via setMatrixAt — the same approach used for the particle work in the galaxy formation snippet, applied to rigid instances instead of points.

The rung dummy object is positioned at strand A's live point, scaled along its local Z axis to the current distance to strand B, and rotated with lookAt(pB) every frame. Because this is recomputed from the live strand positions rather than stored separately, the rung can never desync from its two endpoints.

Deriving each strand point from cos(angle) * RADIUS and sin(angle) * RADIUS guarantees the two backbones stay exactly RADIUS apart and offset by PI radians at every frame — a true double helix. Storing raw XYZ positions for both strands independently would risk them drifting out of the correct geometric relationship as they interpolate.

Yes. Every base pair's angle and height are lerped between fixed start and end values by a single progress number. Scrolling up simply drives that number back toward zero, and because there is no other state (no accumulated physics, no random seed reused per frame), the helix retraces its exact path 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 geometry, InstancedMesh objects, and GSAP ScrollTrigger inside a mount effect keyed to a canvas ref, and on unmount kill the ScrollTrigger instance, dispose geometries and materials, and call renderer.dispose().