Draw My Canvas / studio

Spiral Waves

Four Archimedean arms turning in place, so the bands between them seem to stream inward.

Plate 04archimedean

Waiting for the animation above to load…

What lands in the file, and what the width does

One click writes whatever the animation is drawing at that moment to a PNG, with the drawmycanvas.com mark drawn into the picture rather than laid over it. Leave the width box empty and you get the stage exactly as your browser rasterised it — your window’s width times its device pixel ratio, which is about 1,600 px across from a 1280‑px window on a HiDPI laptop and about 353 px from a 390‑px phone.

Type a width instead, or take a preset, and the frame is redrawn into a canvas that wide: the height follows the stage’s own shape and the mark scales with it. The stage is 16:9 on a wide window and 4:3 below 560 px, so a width of 1200 saves 1200×675 on a laptop and 1200×900 on a phone. 1200 px is the width Open Graph and X link cards are cut from — the canonical card is 1200×630, and a card crops the extra height rather than letterboxing it.

Honest limits. Asking for more pixels than the stage was drawn at resamples pixels that were never drawn: a 1920‑px file exported from a 353‑px phone stage is bigger, not sharper. For a big file that is sharp, use Save as wallpaper: it draws the plate again from scratch at exactly 1179×2556 or 1290×2796 (phones), 1920×1080, 2560×1440 or 3840×2160 (4K), so every line is rasterised at that size and the shape is the screen’s, never stretched. Because the plate restarts, a wallpaper is a fresh run of it rather than the exact frame on screen; it is given as long as the stage has been running, up to 20 seconds, to develop. A very large width is a real memory allocation and a browser is allowed to refuse it; when that happens the line above says so plainly and nothing else on the page changes. Stills are PNG only — no JPEG, no WebP. On browsers that can record video, Record a clip saves 5, 10 or 20 seconds of the running animation as MP4 or WebM (whichever this browser can encode) at the same width, with the mark in every frame. A clip is not a seamless loop, it has no audio, and a width bigger than the stage is resampled rather than sharper — only the wallpaper is redrawn at its size. Where a browser cannot record video the button never appears and a PNG is the only export. And nothing is uploaded: the frame or clip is assembled in your own browser, so no frame of this plate ever reaches us.

Live on an HTML canvas · vanilla JavaScript · no dependencies Open fullscreen

About this piece

Four thin spiral arms leave the middle of the frame, two in cobalt and two in coral, and the whole set turns slowly in place. Nothing else is drawn. Each arm is one stroked line of 90 straight segments, and every frame is wiped and redrawn from a formula, so there is no trail, no fading and no accumulated history: what you see at any instant is the complete picture.

The curve is the Archimedean spiral, the one Archimedes studied in his treatise On Spirals in the 3rd century BC. A point moves out along a line at a steady speed while the line itself turns at a steady speed, so the distance from the centre grows in exact proportion to the angle turned. In polar coordinates that is

r = b (θ − φ)

where θ is the direction you are looking in, r is how far out the arm is in that direction, φ is the direction the arm leaves the centre in, and b sets how loosely it winds. Its defining property is that a ray from the centre crosses successive turns of one arm at a constant spacing, 2πb. That is why it is also called the arithmetic spiral: the crossings go up like 1, 2, 3, 4, not like 1, 2, 4, 8.

This plate’s numbers

Every arm sweeps 3.4π radians, which is 1.7 turns, and ends at 0.44 of the frame’s short edge, so the outer tips never touch the border. Putting those two together gives b = 0.44 × short edge ÷ (3.4π), about 4.1% of the short edge per radian. One arm alone would leave a gap of 2πb, about 26% of the short edge, between its own turns.

The four arms are a quarter-turn apart (their φ values differ by π/2), so they split that gap into four equal parts. Along any ray from the centre, neighbouring arms are always bπ/2 = 0.44 ÷ 6.8 of the short edge apart, which is about 6.5%. On a stage 500 pixels tall that is 32 pixels of bare paper between every pair of strokes. The spacing is the same at every radius and at every moment, so the arms never cross or touch: an Archimedean spiral and a rotated copy of itself are nested curves, not intersecting ones. The even, untouched bands between the strokes are the whole design.

Why the bands seem to stream inward

The figure only ever rotates. Each arm’s starting direction is φ = 0.4t + a quarter-turn per arm, and t only grows. On a canvas the y axis points down, so a growing angle turns clockwise on screen, and the arms themselves wind clockwise as they go outward. Yet what the eye reports is not turning but flow: the bands seem to stream inward, sinking into the centre while new ones appear at the rim.

The formula says why, in one line. Turn the spiral by an extra angle α and every arm becomes r = b(θ − φ − α) = b(θ − φ) − bα. In every direction, every arm is now exactly bα closer to the centre than before. For an Archimedean spiral, rotating it and sliding every arm radially are the same picture, and your visual system, which has no way to tell them apart along most of the length, takes the simpler-looking reading of steady radial motion. It is the same trick as a barber’s pole, where a helix turning about its axis is seen as stripes climbing it.

The speed follows from the code. The phase grows by 0.4 radians per unit of animation time, so the arms move inward at 0.4b, about 1.6% of the short edge, per unit. One arm-spacing (bπ/2) therefore passes any point every π ÷ 0.8 ≈ 3.93 time units. The animation adds 0.03 time units per frame it draws, which makes that 131 frames: about 2.2 seconds at 60 frames a second. Because the four arms are identical in shape, the figure looks the same again every 2.2 seconds; because they alternate in colour, the cobalt and coral bands trade places each time, and the coloured pattern repeats every 4.4 seconds. A full revolution takes about 8.7 seconds.

Archimedean, not logarithmic

The spiral usually cited for nautilus shells and the arms of spiral galaxies is a different curve, the logarithmic spiral. On that one the distances between successive turnings grow in a geometric progression, so every turn is the same shape as the last, only bigger; on the Archimedean spiral those distances are constant. Even there the match is loose: the nautilus is described as only approximately logarithmic, and real galaxy arms change their pitch angle with distance from the centre, which a true logarithmic spiral does not. This plate draws neither of those things. Its arms are evenly spaced, like the groove on a record or a rolled-up carpet seen end on, and that even spacing is exactly what makes the streaming illusion work — a rotating logarithmic spiral would seem to zoom rather than flow.

Honest limits

The speed is counted in frames, not seconds. The clock advances a fixed 0.03 every time the browser paints, so the 2.2-second figure holds only on a 60 Hz screen. On a 120 Hz display the bands stream twice as fast, one spacing every 1.1 seconds; on a busy laptop that drops frames they slow down. The gallery thumbnail runs on the same frame clock.

The illusion is only true in the middle of the arms. The formula above treats the spiral as endless, but each arm here starts at the centre and stops at 0.44 of the short edge. Those two ends visibly rotate rather than stream: the four arm tips sweep round the rim clockwise, and at the centre, where all four arms begin at the same point, their innermost hooks spin like the blades of a pinwheel. Watch either end and the flow turns back into spin.

Despite the name, nothing here is a wave in the physical sense. No two arms add, cancel or interfere; the “waves” are the bare bands between strokes, moving because the strokes do. And under prefers-reduced-motion the plate draws a single still frame and never animates, so the streaming is not shown at all.

Draw your own

The bands are not drawn at all. What you see as a wave is the bare ground left between four strokes, and the whole trick is in how those strokes are offset. The general Archimedean spiral is r = a + bθ: a point starts at distance a from the centre and moves outward at a steady speed while its direction turns at a steady rate, and b is simply the outward speed divided by the turning speed. One arm’s successive turns are always 2πb apart. This plate draws four copies of one arm, each rotated a quarter-turn (φ = 0, π/2, π, 3π/2) from the last. Rotating an arm by φ is the same as setting a = −bφ, so the four arms are really one spiral with four different starting distances, each bπ/2 further in than the last — and that is why the gaps between them come out as four identical, evenly spaced bands that never narrow, widen or cross.

The snippet below is that idea in 25 lines of HTML and plain JavaScript, with no library. With b = 20 px per radian, one arm’s own turns sit 2π × 20 ≈ 126 px apart, the four arms split that into bands 20π/2 ≈ 31.4 px apart, and a 3 px stroke leaves about 28 px of bare ground in each band. Each arm sweeps 3.4π radians (1.7 turns) and ends 20 × 3.4π ≈ 214 px from the centre, inside a 480 px canvas. It paints with the same cobalt, coral and ground this page’s animation reads from the site palette, light or dark to match your device. Save it as an .html file and open it.

<canvas id="spiral" width="480" height="480"></canvas>
<script>
const dark = matchMedia('(prefers-color-scheme: dark)').matches;  // follow the OS theme
const [bg, cobalt, coral] = dark ? ['#0B0D12', '120,158,255', '255,120,84']
                                 : ['#E7E2D5', '52,88,214', '190,68,28'];
document.body.style.background = bg;
const ctx = document.getElementById('spiral').getContext('2d');
const b = 20;  // px per radian: one arm's own turns sit 2*PI*b = 126 px apart
const arms = 4, maxTheta = 3.4 * Math.PI; let t = 0;  // 1.7 turns per arm
const still = matchMedia('(prefers-reduced-motion: reduce)').matches;
function frame() {
  ctx.clearRect(0, 0, 480, 480); ctx.lineWidth = 3;
  for (let s = 0; s < arms; s++) {
    const phi = t + s * 2 * Math.PI / arms;  // arms a quarter-turn apart
    ctx.strokeStyle = 'rgba(' + (s % 2 ? coral : cobalt) + ',0.8)';
    ctx.beginPath();
    for (let th = 0; th <= maxTheta; th += 0.05)  // r = b * theta
      ctx.lineTo(240 + b * th * Math.cos(th + phi), 240 + b * th * Math.sin(th + phi));
    ctx.stroke();
  }
  t += 0.012;  // turning by 0.012 rad = sliding every arm b * 0.012 px inward
  if (!still) requestAnimationFrame(frame);
}
frame();
</script>
Paste it into an empty .html file.

The motion is the same 0.012 radians per frame the plate turns by. Because rotating by φ only changes a, each frame slides every arm 20 × 0.012 = 0.24 px inward, so one band passes any point every 31.4 ÷ 0.24 ≈ 131 frames, about 2.2 seconds at 60 frames a second. Under prefers-reduced-motion it draws one still frame and stops.

Where to take it: set arms to 2 and the bands double to about 63 px; set it to 8 and they shrink to about 16 px, with only 13 px of ground left between strokes. Halve b to 10 and everything tightens by half, including the outer reach (about 107 px). Write the radius as 40 + b * th instead of b * th and you have given the spiral an a of 40 px: a bare 40 px hole opens in the middle, like the label on a record. Change th + phi to phi − th in both places and the arms wind the other way, so the same clockwise turn now makes the bands stream outward instead of in. What the snippet does not do: its speed is counted in frames, so it runs twice as fast on a 120 Hz screen; it reads your light or dark setting once, when the page loads; and it draws at 480 CSS pixels without scaling for high-density screens, so the lines are slightly soft on a phone or a Retina display.

Curious how the loop and canvas fit together? Read how it works →

More from the gallery

All 32 animations in the gallery →