Lissajous Curves
A glowing dot travels a 3-to-4 Lissajous curve and leaves a 500-point rainbow trail that fades at the tail, on a crisp Retina canvas.
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
At each step the dot sits at x = sin(A·t), y = sin(B·t + π/2), with A = 3 and B = 4: the figure an oscilloscope shows in XY mode when fed two sine waves at frequencies 3 and 4. It closes every 2π, i.e. 314 steps of 0.02, about 5.2 s at the default pace of 60 points per second. That pace is counted in elapsed time (capped at 50 ms per frame), so the drawing moves as fast on a 60 Hz screen as on a 120 Hz one.
The script keeps the last 500 positions. On each frame it erases 3 % of the previous image towards transparency, then redraws all 500 points as 2 px discs: opacity rises from 0 for the oldest to nearly 1 for the newest, and hue moves 0.5° per point while also cycling over time. The figure is centred and spans 80 % of the smaller half-dimension of the area: on 800 × 300 px it fits in a 240 px square.
Size comes from the displayed canvas (read from the area, multiplied by devicePixelRatio) and a ResizeObserver follows it; a size change restarts the trail at the centre of the new area. While the area is 0 px, nothing runs. An IntersectionObserver stops the loop off screen. Each .fx-0612-root on the page gets its own instance, marked with data-lsj-ready: pasting the script twice does not start two traces in the same area.
Accessibility
- Label:
role="img"andaria-labelon the canvas. As pure decoration (animated logo next to a name, background), switch it toaria-hidden="true". - Reduced motion: under
prefers-reduced-motion: reduce, the complete figure is drawn once, in solid colours, and stays still. - Pausing: no built-in button. Toggling the root's
hiddenattribute stops the computation (zero size) and resumes it when shown again, which is enough to offer the control WCAG 2.2.2 asks for. - Colour: the rainbow is decorative and carries no information; there is nothing to read in the curve.
- Keyboard: no interactive element in the effect, no focus to manage.
Browser compatibility
2D canvas and about 4 KB of JavaScript, no dependency. ResizeObserver: Chrome 64, Firefox 69, Safari 13.1; IntersectionObserver: Chrome 51, Firefox 55, Safari 12.1 (MDN data). Measured in Chromium 151, Firefox 153 and WebKit 26.5: 2× density rendering, two independent copies, start after a delayed reveal, still figure under reduced motion, no frames computed off screen.
Without ResizeObserver, the figure only re-centres on window resize; without IntersectionObserver, the loop keeps running off screen. Before Safari 14, switching the reduce-motion setting needs a reload. Without JavaScript, the area stays empty on the root's background.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0612-root">
<canvas class="lsj-canvas" role="img" aria-label="Lissajous curve traced by a glowing dot leaving a rainbow trail"></canvas>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Frequencies (JS — A, B) |
3 and 4 | 1 and 2 give a plain parabola traced back and forth, 5 and 4 a denser figure. Two coprime integers always close the curve. |
Curve step (JS — STEP) |
0.02 | Smaller brings the points closer so the curve looks continuous, but it moves more slowly. |
Speed (JS — POINTS_PER_S) |
60 points per second | Double it while halving STEP: same speed, twice as dense a trail. |
Trail length (JS — LENGTH) |
500 points | 300 leaves a short snake that does not cover the whole figure; 800 keeps almost all of it. |
Figure size (JS — SIZE) |
0.8 (80 % of the half-area) | 0.95 hugs the edges, 0.5 leaves breathing room, for a logo for instance. |
Thickness (JS — RADIUS) |
2 px | 1 for a thin oscilloscope line, 3 or 4 for a bolder graphic look. |
Background (CSS — background of .fx-0612-root) |
#0a0a0f | Trails fade to transparency, so this colour is the only one to change. On a light background the 60 % lightness hues stay readable. |
Height (CSS — --lsj-height) |
300px | Any fixed height, or 100% inside a parent of known height; the figure re-centres itself. |
FAQ
STEP is set in the equation, not in pixels: the bigger the figure, the further apart two successive points land. From about 40 px of half-width, the discs separate where the curve moves fastest. For a large visual, divide STEP by two or three and multiply POINTS_PER_S and LENGTH by the same factor: same speed, same trail length, smoother line.addPoint() to take samples from two Web Audio API AnalyserNodes instead, which is beyond the scope of this effect.B a slightly fractional value (4.01 for instance): it will no longer close exactly.