Spirograph
A rosette of sixteen off-centre rings: half turn one way, half the other, and the two rosettes weave through each other like a spirograph.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. SVG has 30 effects, including 3 free. Explore the category →
3 usage examples



How it works
The script creates data-rings rings (16 by default) inside the centre point .spg-stage. Each ring is a 120 px circle (--spg-base) whose centre sits 50 px (--spg-offset) away from the area's centre, at an evenly spread starting angle: 0°, 22.5°, 45°… Stacked, these off-centre circles draw a rosette, the shape a spirograph traces.
Rotation goes through a single animation, spgSpin: translate(-50%, -50%) rotate(angle) translateX(offset), the angle running from its start to start + 360°. Because the circle is off-centre, turning it really makes it travel around the middle. Even rings turn clockwise, odd ones counter-clockwise (.spg-ring--reverse), all at 12 s per turn. So two rosettes of eight rings cross each other: they merge into a sixteen-petal rosette, split, weave diamonds, then meet again.
Colours run from blue to pink: hue 220° plus 10° per ring (data-hue, data-hue-step), with opacity easing down from 0.85 to just under 0.5. The --spg-play variable pauses (paused) or resumes (running) the whole rosette without redrawing it. --spg-grow and --spg-drift, 0 by default, give the rings different sizes and speeds.
Accessibility
- Decoration: the ring container carries
aria-hidden="true". In a timer or a file upload, the remaining time and the state must be written as text (role="timer",aria-live). - Reduced motion: under
prefers-reduced-motion: reduce, the animation is removed and each ring stays at its starting angle: the full rosette shows, still. - No interaction: nothing takes focus, nothing reacts to the pointer. The
--spg-playsetting lets your own button pause the rotation. - Continuous motion: two groups turning in opposite directions create a swirling feel. On a reading page, prefer a slow speed (
--spg-speedof 20 to 30 s) or a spot that does not stay in view all the time.
Browser compatibility
CSS plus a dozen lines of JavaScript to create the rings. CSS variables used inside @keyframes and NodeList.forEach set the minimum versions taken from MDN data. Measured in Chromium 151, Firefox 153 and WebKit 26.5: 16 rings per copy, rotation visible in all three engines, two independent copies, still rosette under reduced motion.
Without JavaScript the area stays empty. Browsers without CSS variables do not show the rosette.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0615-root" data-rings="16">
<div class="spg-stage" aria-hidden="true"></div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
data-rings (root) |
16 | Number of rings (1 to 40), evenly spread. An even number gives two equal rosettes turning in opposite directions. |
data-hue / data-hue-step (root) |
220 / 10 | Hue of the first ring and hue step between two rings (−60 to 60). A step of 1 or 2 gives an almost single-colour rosette. |
--spg-base |
120px | Ring diameter. |
--spg-offset |
50px | Distance between a ring's centre and the area's centre. 0 stacks every ring into a single circle that looks still; larger opens the rosette. |
--spg-speed |
12s | Length of one full turn. |
--spg-play |
running | paused freezes the rosette where it is; switching back to running resumes it. |
--spg-grow / --spg-drift |
0px / 0 | Size increase per ring and speed gap per ring (0.1 = each ring 10% slower than the previous one). They break the rosette's symmetry. |
--spg-line / --spg-height / --spg-bg |
1px / 300px / #0a0a0f | Stroke width, area height and background. The rosette stays centred; if it is larger than the area, it gets cropped. |
FAQ
root.style.setProperty('--spg-play', 'paused'), then 'running' to go on. The rosette resumes exactly where it stopped.