SVG✨ Premium

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.

CanvasMathCurves

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

Monitor screen on a synth module product page — Lissajous Curves example 1

① Monitor screen on a synth module product page

WhenOn the Vexmorel shop, the LX-34 front panel is drawn in CSS and its small XY screen shows the curve being traced, next to the price and specs.
WhyA 3:4 figure is exactly what a dual oscillator's screen would show: the animation demonstrates what the product does instead of a still photo.
SettingsRoot inside a 186 × 118 px screen (--lsj-height:100%), giving a figure 47 px in half-height; a linear-gradient graticule sits on top, transparent to the pointer. Default script settings.
Main visual of an online maths course hero — Lissajous Curves example 2

② Main visual of an online maths course hero

WhenOn the Quadrivel home page, the right half of the hero holds the curve being drawn, with its equations as a caption; on the left, the headline, the year groups and the free trial.
WhyThe figure is a function you watch being built: it says "maths made visible" better than a stock illustration.
SettingsRoot in position:absolute over 52 % of the width and full height; the figure takes 80 % of the half-width, the smaller of the two (≈ 112 px). The caption x = sin 3t · y = sin(4t + π/2) mirrors the A and B constants.
Animated logo in a science podcast header — Lissajous Curves example 3

③ Animated logo in a science podcast header

WhenIn the Sinéphore header, a 48 px tile acts as the logo: the curve keeps tracing inside it, next to the name and above the episode list.
WhyAt small size the coloured trail becomes a recognisable signature that speaks of waves, the podcast's topic, without taking up space.
Settings48 × 48 px root with rounded corners (overflow:hidden on the parent); the figure is 19 px in half-width and the discs keep their 2 px radius.

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" and aria-label on the canvas. As pure decoration (animated logo next to a name, background), switch it to aria-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 hidden attribute 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.

Chrome 64+✓ Full (measured)
Firefox 69+✓ Full (measured)
Safari 13.1+✓ Full (measured in WebKit)
Edge 79+✓ Full (Chromium engine)
Safari iOS 13.4+✓ Full (WebKit, not checked on a device)
Chrome Android 64+✓ Full (Chromium engine)

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):

index.html — structure
<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 / propertyDefaultEffect
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.
Not as is: the curve is computed from two sine waves and reads no sound. A real oscilloscope would need addPoint() to take samples from two Web Audio API AnalyserNodes instead, which is beyond the scope of this effect.
No: with integer frequencies and a fixed π/2 phase, the shape never changes; only the dot moves and the colours cycle. For a figure that slowly shifts, like a slightly detuned oscilloscope, give B a slightly fractional value (4.01 for instance): it will no longer close exactly.