GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

Lorenz Attractor: 9 Butterfly Chaos Demos

Integrate three simple equations step by step and a butterfly-shaped orbit draws itself. Copy-paste chaos, zero libraries.

Auto-plays · click a tile to jump to its section · all nine in one zip

From One Euler Integration to Nine Readings of the Lorenz Butterfly

The nine scenes share one axis: what each does with the trajectory once the integration is done. 01 keeps the line itself, revealed in order so the butterfly draws itself. 02 keeps the line but moves the eye, spinning and tilting it through an oblique projection. 03 turns the question onto the integrator — Euler against fourth-order Runge-Kutta at the same step size. 04 steps outside flows entirely into two 2D recurrences, Clifford and de Jong, where the image is pure visit counting. 05 returns to flows carrying two other systems, Aizawa wrapping a sphere and Thomas spooling thread. 06 swaps the single point for 2,000 and reads the same equations as statistics. 07 folds the 3D flow into a 2D map by keeping only the instants the orbit pierces a plane. 08 hands the two live coefficients to the pointer. 09 stacks cloud, arm, vignette, and type into a front-door scene. Across a 24-frame sample, eight of the nine items transition on 23 of 24 frames, and item 04 on 19 of 24 — its three color channels surface in sequence, so a few sampled frames land between channel steps.

# Item Technique Painted area Avg intensity
01 Butterfly basics Euler dt 0.01, 3,000 steps, 2s reveal 4.882% 51.6
02 3D rotation oblique projection, scale W/57 and H/37.5 28.715% 102.4
03 RK4 integration Euler vs RK4, both dt 0.02 11.656% 51.9
04 Attractor atlas Clifford and de Jong, 20,000 iterations per channel 8.677% 15.5
05 Aizawa and Thomas two other systems, RK4 dt 0.02 0.768% 60.7
06 Density cloud 2,000 points, visit histogram 1.04% 72.7
07 Poincare section plane crossings at z = 27, RK4 dt 0.005 3.425% 25.9
08 Parameter knobs pointer sets sigma and rho, 60ms lag 9.233% 37.6
09 Hero scene density fog, bright arm, vignette, type 10.849% 67.5

Code per item

Every fence below is copied from that item's own source file — same constants, same identifier names — with only the comments translated. Each block closes complete, nothing is elided, and the zip carries the nine full files.

01Butterfly basics — three equations, one Euler step at a time

Sigma 10, rho 28, and beta 8/3 integrated with Euler at dt 0.01 for 3,000 pre-computed steps, revealed in order over a 2s cycle that ends at phase 0.82 before fading — 4.882% painted area, a 3.886% peak frame, and 51.6 average intensity. The first 200 steps are integrated and thrown away so the reveal starts on the attractor, and the orbit's 24 chunks of average y drive the depth shading.

Butterfly basics — three differential equations, one Euler step at a time
Open in a new tab

01 · Butterfly basics — three differential equations, one Euler step at a time — Measured area 4.882% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

CanvasRenderingContext2DrequestAnimationFramelineTo

The whole system in two functions

  // Lorenz velocities — returns dx, dy, dz
  function deriv(x, y, z) {
    return [SIG * (y - x), x * (RHO - z) - y, x * y - BETA * z];
  }

  // pre-integration — skip 200 transient steps so the reveal starts on the attractor
  var xs = new Float32Array(STEPS), ys = new Float32Array(STEPS), zs = new Float32Array(STEPS);
  var x = 0.6, y = 0.7, z = 21, i;
  for (i = 0; i < 200; i++) {
    var d0 = deriv(x, y, z);
    x += d0[0] * DT; y += d0[1] * DT; z += d0[2] * DT;
  }
  for (i = 0; i < STEPS; i++) {
    xs[i] = x; ys[i] = y; zs[i] = z;
    var d1 = deriv(x, y, z);
    x += d1[0] * DT; y += d1[1] * DT; z += d1[2] * DT;
  }

023D rotation — the orbit spinning through oblique projection

A 2s rotation about z and a tilt sweeping 0.10 to 1.00 rad on a 14s period feed an oblique projection whose horizontal component spans two axes, so the thread never collapses at any angle — 28.715% painted, the largest of the nine, with an 11.488% peak and the set's brightest average intensity at 102.4. The anisotropic scale, width W/57 and height H/37.5, is what fills both edges of the wide 2.4:1 stage.

3D rotation — spinning the orbit through oblique projection
Open in a new tab

02 · 3D rotation — spinning the orbit through oblique projection — Measured area 28.715% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

requestAnimationFrameMath.sinMath.cos

Rotating, tilting, then projecting

    var a1 = tSec / SPIN_S * 6.2832;    // z-axis rotation angle — the fast axis, one turn every 2s
    var a2 = Math.sin(tSec / SPIN_L * 6.2832) * 0.45 + 0.55;  // view tilt 0.10~1.00rad on a 14s period — never crosses 0
    var ca1 = Math.cos(a1), sa1 = Math.sin(a1);
    var ca2 = Math.cos(a2), sa2 = Math.sin(a2);

    // anisotropic scale — the canvas is a wide 2.4:1 window while the attractor is nearly 1:1,
    // so tying both axes to one edge leaves more than half the width empty
    var sx = W / 57, sy = H / 37.5;
    var ox = W / 2, oy = H / 2;

    // oblique projection — after the z rotation (u, w), the tilt a2 bleeds z into the horizontal,
    // so the horizontal spans two axes and the thread never collapses at any angle
    function pr(i) {
      var u = xs[i] * ca1 - ys[i] * sa1;
      var w = xs[i] * sa1 + ys[i] * ca1;
      var zv = zs[i] - 25;
      return [ox + (u * ca2 + zv * sa2) * sx, oy - (zv * ca2 * 0.66 + w * 0.40) * sy];
    }

03RK4 integration — Euler versus fourth-order Runge-Kutta

Both panels integrate the same seed for 2,400 steps at dt 0.02 — the size where Euler's first-order error starts bending the orbit while RK4's four-slope average holds the shape, its error shrinking with the fourth power of the step so the gap between the two panels widens every lap — 11.656% painted, 10.342% peak, and 51.9 intensity. The 2s reveal ends at phase 0.84.

RK4 integration — stepping up from Euler to fourth-order Runge-Kutta
Open in a new tab

03 · RK4 integration — stepping up from Euler to fourth-order Runge-Kutta — Measured area 11.656% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

requestAnimationFramelineToarc(

One slope versus four

  // one Euler step — a single slope moves the position
  function euler(p) {
    var d = deriv(p[0], p[1], p[2]);
    return [p[0] + d[0] * DT, p[1] + d[1] * DT, p[2] + d[2] * DT];
  }
  // one RK4 step — the weighted average of four slopes k1 to k4 (1/6, 1/3, 1/3, 1/6)
  function rk4(p) {
    var k1 = deriv(p[0], p[1], p[2]);
    var k2 = deriv(p[0] + k1[0] * DT / 2, p[1] + k1[1] * DT / 2, p[2] + k1[2] * DT / 2);
    var k3 = deriv(p[0] + k2[0] * DT / 2, p[1] + k2[1] * DT / 2, p[2] + k2[2] * DT / 2);
    var k4 = deriv(p[0] + k3[0] * DT, p[1] + k3[1] * DT, p[2] + k3[2] * DT);
    return [
      p[0] + DT / 6 * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]),
      p[1] + DT / 6 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]),
      p[2] + DT / 6 * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2])
    ];
  }

04Attractor atlas — Clifford and de Jong point clouds

Six coefficient sets — three Clifford, three de Jong — each run 20,000 iterations per channel through a seeded RNG, visits accumulate into a 150px buffer, and the three color channels surface in sequence on a 2s cycle: 8.677% painted, 8.644% peak, and 15.5 intensity. The first 400 steps of each run are the pre-attractor transient, discarded before counting.

Attractor atlas — Clifford and de Jong, probabilistic flowers from millions of points
Open in a new tab

04 · Attractor atlas — Clifford and de Jong, probabilistic flowers from millions of points — Measured area 8.677% · moving frames 19/23Original by GODRICH · SCSS and React ship in the zip · See the password

requestAnimationFrameMath.sinfillRect

Feed a recurrence, count the visits

  function accumulate(set, upto) {        // kind 0 = Clifford, 1 = de Jong
    var rand = rng(set.kind * 1000 + 7);
    var x = rand() * 2 - 1, y = rand() * 2 - 1;
    var n = Math.floor(ITERS * upto);
    var a = set.p[0], b = set.p[1], c = set.p[2], d = set.p[3];
    var i, px, py;
    for (i = 0; i < n + 400; i++) {
      if (set.kind === 0) {
        var nx = Math.sin(a * y) + c * Math.cos(a * x);
        y = Math.sin(b * x) + d * Math.cos(b * y);
        x = nx;
      } else {
        var nx2 = Math.sin(a * y) - b * Math.cos(x);
        y = Math.sin(c * x) - d * Math.cos(y);
        x = nx2;
      }
      if (i < 400) continue;              // transient discarded
      px = (x + 3) / 6 * (RES - 1);
      py = (y + 3) / 6 * (RES - 1);
      if (px >= 0 && px < RES && py >= 0 && py < RES) {
        acc[(py | 0) * RES + (px | 0)] += 1;
      }
    }
  }

05Aizawa and Thomas — other equations, other sculptures

Aizawa (a sphere-wrapping flow) and Thomas (a thread-spool spiral) integrate with RK4 at dt 0.02 for 1,800 steps each under a slow 12s observation-axis rotation — 0.768% painted, 0.444% peak, and 60.7 intensity. The thin single-strand trajectories are the material, not a defect.

Aizawa and Thomas — a sphere and a spiral, different equations, different sculptures
Open in a new tab

05 · Aizawa and Thomas — a sphere and a spiral, different equations, different sculptures — Measured area 0.768% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

requestAnimationFrameMath.sinlineTo

Two more velocity fields

  function dAizawa(x, y, z) {
    var a = 0.95, b = 0.7, c = 0.6, d = 3.5, e = 0.25, f = 0.1;
    return [
      (z - b) * x - d * y,
      d * x + (z - b) * y,
      c + a * z - z * z * z / 3 - (x * x + y * y) * (1 + e * z) + f * z * x * x * x
    ];
  }
  // Thomas b = 0.19 — cyclically interlocking spirals
  function dThomas(x, y, z) {
    var b = 0.19;
    return [Math.sin(y) - b * x, Math.sin(z) - b * y, Math.sin(x) - b * z];
  }

06Density cloud — visit counts as pixel brightness

2,000 points seeded near the core with a fixed RNG advance two Euler steps per frame at dt 0.016, and their xz projection accumulates into a 150px histogram — the trajectory read as statistics — with a 2s reset before the pile rebuilds: 1.04% painted, 0.934% peak, and 72.7 intensity.

Density cloud — visit counts piling into pixel-brightness fog
Open in a new tab

06 · Density cloud — visit counts piling into pixel-brightness fog — Measured area 1.04% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

requestAnimationFramefillRectMath.min

Step the swarm, then count the passes

  // Euler integration — two steps per frame
  function stepAll() {
    var i, k, dx, dy, dz;
    for (k = 0; k < 2; k++) {
      for (i = 0; i < NPTS; i++) {
        var x = px[i], y = py[i], z = pz[i];
        dx = SIG * (y - x); dy = x * (RHO - z) - y; dz = x * y - BETA * z;
        px[i] = x + dx * DT; py[i] = y + dy * DT; pz[i] = z + dz * DT;
      }
    }
  }

  // visit counts into the histogram buffer — xz projection
  function accumulate() {
    var i, gx, gy;
    for (i = 0; i < NPTS; i++) {
      gx = (px[i] / 22 + 0.5) * (RES - 1);
      gy = (1 - (pz[i] - 1) / 47) * (RES - 1);
      if (gx >= 0 && gx < RES && gy >= 0 && gy < RES) {
        hist[(gy | 0) * RES + (gx | 0)] += 1;
      }
    }
  }

07Poincare section — stamping only the plane crossings

RK4 at dt 0.005 for 14,000 steps (70 time units) walks the orbit densely, and whenever consecutive z values straddle the plane at z = 27 the crossing point is linearly interpolated and stamped — upward crossings in ice pastel, downward in peach, each splatted as a 3x3 Gaussian-ish kernel onto the 150px map: 3.425% painted, 2.651% peak, and 25.9 intensity.

Poincare section — stamping only the moments the orbit pierces a plane
Open in a new tab

07 · Poincare section — stamping only the moments the orbit pierces a plane — Measured area 3.425% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

requestAnimationFramelineToMath.min

The interpolation is the accuracy

  // crossings — the interpolated (x, y) and direction at each instant z straddles the plane (1 up, 0 down)
  var crX = [], crY = [], crUp = [];
  for (i = 1; i < STEPS; i++) {
    var za = zs[i - 1] - Z_PLANE, zb = zs[i] - Z_PLANE;
    if ((za < 0 && zb >= 0) || (za >= 0 && zb < 0)) {
      var t2 = za / (za - zb);
      crX.push(xs[i - 1] + (xs[i] - xs[i - 1]) * t2);
      crY.push(ys[i - 1] + (ys[i] - ys[i - 1]) * t2);
      crUp.push(zb > za ? 1 : 0);
    }
  }

08Parameter knobs — the pointer twists sigma and rho

Pointer x targets sigma 4 to 24 and y targets rho 10 to 45, twelve seeded orbits re-integrate as the coefficients move, and the values ease toward their targets with a 60ms time constant so the butterfly squashes and rebirths instead of snapping — 9.233% painted, 5.117% peak, and 37.6 intensity. A divergence guard freezes any orbit that escapes past 900.

Parameter knobs — pointer twists sigma and rho, the shape answers
Open in a new tab

08 · Parameter knobs — pointer twists sigma and rho, the shape answers — Measured area 9.233% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

pointermoverequestAnimationFrameCanvasRenderingContext2D

Pointer to coefficient, with a lag

  function setTarget(ev) {
    var r = fieldEl.getBoundingClientRect();
    var px = (ev.clientX - r.left) / r.width;
    var py = (ev.clientY - r.top) / r.height;
    px = Math.max(0, Math.min(1, px));
    py = Math.max(0, Math.min(1, py));
    sigT = 4 + px * 20;                   // sigma 4~24
    rhoT = 45 - py * 35;                  // rho 10~45 — the top of the field is high rho
    hasPtr = true;
  }
  fieldEl.addEventListener('pointermove', setTarget);
  fieldEl.addEventListener('pointerdown', setTarget);
  fieldEl.addEventListener('pointerleave', function () {
    sigT = 10; rhoT = 28;                 // pointer gone — back to the classic coefficients
    hasPtr = false;
  });

09Butterfly hero scene — a front door over the density fog

The 2,400-point cloud at dt 0.016 lays the backdrop, a pre-integrated bright orbit creeps across it over a 12s cycle under a 30s observation rotation, and a vignette plus three type lines — CHAOS BUTTERFLY, Lorenz attractor simulator, dz/dt = xy − βz — finish the scene: 10.849% painted, 3.231% peak, and 67.5 intensity.

Butterfly hero scene — a front-door stage layered with type and particles
Open in a new tab

09 · Butterfly hero scene — a front-door stage layered with type and particles — Measured area 10.849% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

requestAnimationFramecreateRadialGradientMath.sin

The arm and its slow clock

    // the bright arm — a pre-integrated orbit of 700 points, read by tSec over a 12s cycle
    var armT = (tSec % 12) / 12;
    var armN = 700;
    var ai = Math.floor(armT * armN);
    var s2 = Math.min(W, H) / 52;
    var ca2 = Math.cos(tSec / SPIN_L * 6.2832), sa2 = Math.sin(tSec / SPIN_L * 6.2832);  // observation angle, 30s per turn
    ctx.lineCap = 'round';
    ctx.strokeStyle = rgba(HEAD, 0.5);
    ctx.lineWidth = 2.2;
    ctx.beginPath();
    var a0 = Math.max(0, ai - 160), hx2, hy2, p2, u2, v2;
    for (i = a0; i <= ai; i++) {
      p2 = armXZ(i);
      u2 = p2[0] * ca2 - p2[1] * sa2;
      hx2 = ox + (u2 / 22 + 0.5) * side;
      hy2 = oy + (1 - (p2[2] - 1) / 47) * side;
      if (i === a0) ctx.moveTo(hx2, hy2); else ctx.lineTo(hx2, hy2);
    }
    ctx.stroke();

Why does the same formula trace a different path every time — chaos and sensitivity

The butterfly is the orbit, not an object drawn in advance

Nothing on these canvases is a shape that was modeled and then animated. The two lobes exist only as the set of places the point has visited — every stroke of brightness is a visit, and the wings emerge because the flow keeps folding the trajectory back into the same two regions. Stop on any item and you are looking at a scatter of history, not a drawing.

Deterministic and unpredictable are one fact

The Lorenz attractor equations contain no randomness at all: same start, same coefficients, same steps, and the path reproduces exactly — which is precisely what the seeded RNGs and fixed starting points rely on for deterministic playback. The unpredictability sits one millimeter away. Move the start by a hair and the early laps still track closely, then one lap lands on the other wing and the two histories share nothing afterward. Item 08 shows this live: drag rho a little and within a lap the orbit is somewhere the old orbit never went.

Why the wings hold their shape at all

The orbit circles one lobe a few times, the z coordinate crests, and the crossing of the middle sends it hopping to the other lobe — a rhythm, not a random walk. Item 07 makes the rhythm visible: stamp only the z = 27 crossings and the dots fall into bands, one per wing, because the crossings cluster where the flow's folding is tightest. The butterfly shape is the statistics of that hopping, which is why every rendering style here — line, fog, section map — surfaces the same two wings.

The same family as the other computed fields on this site

Each scene advances state through a field recomputed from nothing but the current state — the same premise as an N-body gravity scene where each star tugs on all the others, the cardioid set that grows new detail at every depth, and a reaction-diffusion plate where two chemicals trade patterns. A strange attractor is the restless member of that family: the other fields let state settle into a pattern, while this one never lets the point stop moving — the pattern is where it wanders, forever.

One of three variants wires straight into reduced-motion accessibility

Three knobs to turn

Name Change Feel
Time step item 01 Euler dt 0.01 → 0.005 the trajectory smooths and the same screen time carries twice the steps
Coefficient rho item 08 sigma 10, rho 28 → rho 35 the wings spread wider and the outward dashes come visibly sooner
Point count item 06 cloud 2,000 → 8,000 points the fog thickens and the structure inside the wings brightens like a night-sky galaxy

Reduced motion still shows an accumulated frame

Each scene looks at prefers-reduced-motion before starting its loop, and when the preference is set the loop is never scheduled. One accumulated scene renders at a fixed phase and freezes with an is-still class on the root, so the frozen frame still shows a complete butterfly instead of an empty rectangle.

One pointer item, eight that run alone

Item 08 listens to pointermove and pointerdown over its field, and pointerleave restores the classic sigma 10, rho 28 — the other eight run on requestAnimationFrame alone. Hosts expose an aria-label and a tabindex, which keeps the grid keyboard-reachable.

An Euler dt above 0.05 is where the butterfly breaks

Nine boards funnel every failure people hit while building this into four shapes: a step size pushed too far, a projection scale left square on a wide stage, a crossing test done at sample granularity instead of interpolated, and randomness left unseeded. All four keep rendering without a single error — the picture just stops matching what the equations actually describe.

A large Euler dt explodes the butterfly

Euler's error is proportional to the step, and it compounds step after step: at dt 0.05 or larger the trajectory shoots outward and the wings collapse within seconds. Item 03 stages that boundary on purpose at dt 0.02, where the Euler panel already distorts after a few laps; the items that ride Euler alone keep dt between 0.01 and 0.016, and item 08's knob sits at 0.012.

One scale factor empties half of a wide canvas

Tying the projection to min(W, H) on a 2.4:1 stage leaves more than half the width blank, because the attractor is nearly 1:1 in extent. Item 02 scales width by W/57 and height by H/37.5 separately — each edge of the wide canvas gets filled, and that anisotropy is how the panel reaches its 28.715% painted area.

Crossing checks at sample granularity land off the plane

Item 07 integrates at dt 0.005, yet snapping to the nearest sample when z crosses 27 still puts dots visibly off the band. The linear interpolation between the previous z and the current z is the map's accuracy — skip it and the section becomes a smear instead of a structure.

RK4 everywhere busts the frame budget

Fourth-order accuracy costs four derivative evaluations per step. Items 06 and 09 re-integrate 2,000 and 2,400 points every frame — two small Euler steps per point fit that budget, while RK4 at the same particle count would quadruple the derivative calls for accuracy the fog cannot display. Precision belongs where the picture is a single trajectory (03, 05, 07); speed belongs where thousands move at once.

Unseeded randomness and kept transients dirty the atlas

Item 04 seeds a mulberry32 stream per coefficient set and item 06 seeds with 42, so the same frame time always rebuilds the same picture — swap in Math.random and every pass redraws differently, determinism gone. The first 400 recurrence steps in 04 are the pre-attractor transient; keep them and a stray line smears the pattern before the counting even starts. The zip of nine source folders behind this post opens with 32hdtxzf and carries the HTML, CSS, and React port of every scene.

Where this actually gets used — a butterfly hero and a parameter playground

Of the nine boards, three fold into real pages without extra work. Item 09's butterfly hero overlays letterforms and particles on the density fog, so it drops straight into a landing page's opening panel or a course site's intro — the effect reads best over a dark hero section.

Item 08's parameter knob lets a visitor drag sigma and rho directly, which fits an interactive demo block or a teaching page where the point is to let someone feel the coefficients move. Its own divergence guard already keeps the trajectory on screen, so no extra bounds-checking is needed to reuse it as-is.

Item 06's density cloud reads as fog rather than a distinct shape, which makes it a quiet card background or a section divider laid behind text. Dropping the point count trims the per-frame cost for that kind of background role.

Questions before you paste this Lorenz attractor code FAQ

Do these Lorenz attractor js scenes need a library or a build step

No. Each item is one self-contained HTML file — a plain canvas 2D context, hand-rolled integrators, no CDN, and no WebGL — so pasting the file into any page runs it as-is. The zip adds a React port of every scene for projects that live in a component tree.

Why do items 05, 06, and 07 paint so little of the canvas

Their 0.768%, 1.04%, and 3.425% are the honest size of the material. Item 05 draws single-strand trajectories, item 06 accumulates thin histogram fog, and item 07 stamps dots only at the instants the orbit pierces the plane — thickening the strokes or smearing the dots would erase exactly what each item exists to show.

Euler or RK4 — which integrator fits a Lorenz attractor animation

For a fixed trajectory shown once, pre-integrate with RK4 and the picture holds its shape — that is items 03, 05, and 07. For per-frame re-integration of thousands of points, small Euler steps win on cost — items 06, 08, and 09. The two panels of item 03 are the whole argument compressed into one screen.

Sources: MDN — CanvasRenderingContext2D · MDN — Window.requestAnimationFrame() · MDN — prefers-reduced-motion · Wikipedia — Lorenz system · Wikipedia — Attractor · Wikipedia — Runge–Kutta methods

Enter the archive password

The password is inside this article. You will find it as you read.