Spectrum Gradient
Sixteen DOM bars with a rotating HSL gradient — violet to magenta, heights driven by two sinusoids plus noise, no canvas, no library.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Atmosphere has 57 effects, including 10 free. Explore the category →
3 usage examples



How it works
Sixteen <div class="bar"> elements are injected into the .spectrum-gradient container by the init script. On each requestAnimationFrame tick, every bar receives a freshly computed linear-gradient(to top, hsl(h, 80%, 50%), hsl(h+30, 80%, 60%)) via .style.background, and a new height between 10 and 110 px via .style.height. The CSS transition height 0.05s smooths inter-frame jumps without burdening the loop.
The hue h for each bar follows (i / 16) × 60 + 240 + sin(t × 0.002) × 30. The linear term spreads the bars across 60° of the color wheel (violet 240° → magenta 300°). The sinusoidal term oscillates the entire palette by ±30° over ~3 seconds, making the colors appear to breathe together.
Heights come from getSimulatedAudio(n, time): for bar at index i, the local frequency is 1 + i × 0.5 and the value is |sin(t × freq × 0.002) × 0.5 + sin(t × freq × 0.003 + i) × 0.3 + rand × 0.2|, clamped to [0, 1]. Two sinusoidal oscillators at frequencies proportional to the bar index create staggered rise/fall patterns; the 20% random noise component reproduces the irregularity of a real audio signal.
Accessibility
- prefers-reduced-motion not implemented: the code does not detect
prefers-reduced-motion— the RAF runs continuously regardless of OS settings. Must be added manually (see FAQ). - The
.spectrum-gradientcontainer carries neitheraria-hiddennorrole="img"noraria-label— the DOM bars are reachable by screen readers but carry no semantic description. - The effect is purely decorative: no interactive element is attached, and the keyboard tab order is unaffected.
- The
height 0.05stransition stays active regardless of user preferences — animated height changes cannot be disabled through OS settings. - If the effect is embedded in a semantic context, add
aria-hidden="true"to the container or a descriptivearia-labelon the parent element.
Browser compatibility
Requires CSS flexbox, linear-gradient, and requestAnimationFrame — supported in all modern browsers. Zero canvas, zero external dependencies.
If JS is disabled, the 16 bars stay at their initial 10 px height with no gradient — the container renders empty but error-free.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="spectrum-gradient" id="spectrumGradient">
<!-- .bar divs are injected by the JS at init -->
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| spectrumBarCount = 16 | 16 | Number of bars (JS constant). 8 = airy, 32 = dense. Also adjust the CSS gap above 24 bars. |
| .spectrum-gradient { height } | 120px | Container max height (CSS). 200 px for a full-height hero, 60–80 px for a compact widget. |
| .spectrum-gradient .bar { width } | 12px | Width of each bar (CSS). 8 px = tight spectrum, 16–20 px = strong graphic effect. |
| .bars-3d, .spectrum-gradient { gap } | 3px | Gap between bars (CSS, shared rule with .bars-3d). 1 px = dense, 6 px = airy. |
| hue offset +240 | 240 | HSL starting hue in animateSpectrumGradient. 0 = red, 120 = green, 180 = cyan, 240 = violet (default). |
| hue range ×60 | 60 | Hue range across the 16 bars (degrees). 30 = narrow monochromatic palette, 120 = wide rainbow gradient. |
| amplitude ×100 | 100 | Height factor in 10 + data[i] * 100. 40–50 = subtle overlay, 150 = dramatic full-height. |
| rotation speed 0.002 | 0.002 | HSL palette oscillation speed (Math.sin(time * 0.002)). 0.001 = very slow (~6 s/cycle), 0.005 = fast (~1 s/cycle). |
FAQ
<div> elements with a linear-gradient CSS recalculated every frame by requestAnimationFrame. No canvas, no npm dependency. Copy the HTML, CSS, and JS into your project and the effect is ready.getSimulatedAudio(count, time) inside animateSpectrumGradient with a function that reads from an AnalyserNode.getByteFrequencyData() buffer. The bars expect values in [0, 1] — normalize the first 16 bins of the Uint8Array (÷ 255) and pass them directly.const REDUCE = window.matchMedia('(prefers-reduced-motion: reduce)').matches;. In the animate function, conditionally schedule the next frame: if (!REDUCE) requestAnimationFrame(animate); — bars will hold their current height without looping.