Fireflies
30 canvas fireflies each blink at their own sinusoidal frequency — damped Brownian motion, yellow-green radial halo, and toroidal edge wrapping.
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
setupCanvas("psFireflies") sizes the <canvas> to its parent container via getBoundingClientRect(), then 30 particles are created — each with a random position, an initial velocity vector (vx = vy = ±0.25 px/frame max), a starting phase in [0, 2π], and a pulse speed in [0.01 – 0.03] rad/frame.
Each rAF frame, a random impulse of ±0.025 px is added to each velocity component (Brownian noise). A ×0.98 damping factor keeps drift bounded without freezing it — the firefly moves organically. At the four edges, toroidal wrapping teleports the particle to the opposite side: it vanishes on the right and reappears on the left.
Each firefly has its own frequency: n = (sin(frame × speed + phase) + 1) / 2. This scalar oscillates between 0 and 1 and modulates simultaneously core opacity (0.30 + 0.70×n) and effective radius (size + 2×n). With 30 independent phases and speeds, the fireflies never blink in sync.
For each firefly, a createRadialGradient of radius 6×h draws the halo — color rgba(180,230,80, 0.15×n) at center to transparent. The solid core is a filled arc in rgba(200,250,100, 0.30 + 0.70×n). The effect handles neither prefers-reduced-motion, nor IntersectionObserver, nor a public API — it is a self-contained IIFE running continuously from page load.
Accessibility
- prefers-reduced-motion absent: the effect does not detect the system preference. Animation runs unconditionally; add
window.matchMedia('(prefers-reduced-motion:reduce)').matchesto stop the RAF when accessibility is required. - The
<canvas>carries noaria-hidden,role, oraria-labelin the original code — addaria-hidden="true"if the effect is purely decorative. - Core contrast:
rgba(200,250,100)on a#0a0a0fbackground exceeds a 7:1 luminance ratio — above the AAA threshold for graphical elements. - No keyboard or pointer interaction is registered — the canvas is not focusable, keyboard navigation on the page is unaffected.
- Purely decorative effect: no textual or informational content resides in the canvas — overlay content (title, CTA) must live in the DOM, not inside the canvas.
Browser compatibility
Requires Canvas 2D and requestAnimationFrame — supported in all modern browsers. Zero external dependencies.
Without <code>canvas</code> support, the parent container displays with its background — no fatal JS errors, the rest of the page stays functional.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="ps-scene" role="img" aria-label="Animated fireflies in nocturnal flight">
<div class="ps-canvas-wrap">
<canvas class="ps-canvas" id="psFireflies" aria-hidden="true"></canvas>
</div>
<!-- Optional: scrim + overlay content -->
<div class="ps-scrim" aria-hidden="true"></div>
<div class="ps-overlay">
<h2 class="ps-overlay-title">Your message</h2>
<button class="ps-overlay-cta">CTA</button>
</div>
</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 |
|---|---|---|
| Particle count (for e < 30) | 30 | Number of simultaneous fireflies. 10–15 for a subtle card background, 50–60 for a dense full-screen scene. |
| Initial speed (0.5 × (random − 0.5)) | ±0.25 px/frame | Change the 0.5 prefactor to control initial drift. 0.3 = slow, gentle flight; 0.8 = fast, nervous flight. |
| Brownian noise (0.05 × (random − 0.5)) | ±0.025 px/frame | Amplitude of the random impulse per frame. 0.02 = near-linear path; 0.10 = highly erratic, swarming motion. |
| Damping (vx *= 0.98) | 0.98 | Velocity decay factor. 0.96 = rapid deceleration (short paths); 0.995 = near-inertial glide (long, smooth trajectories). |
| Pulse speed range (0.02 × random + 0.01) | [0.01 – 0.03] rad/frame | Blink speed range. 0.04 × random + 0.02 for lively blinks; 0.008 × random + 0.004 for very slow breathing. |
| Base radius range (2 × random + 1.5) | [1.5 – 3.5] px | Core size. 1 × random + 1 = very small, discreet insects; 3 × random + 2 = larger dots, more visible on lighter backgrounds. |
| Halo color (rgba(180,230,80,…)) | yellow-green #b4e650 | Color of the radial gradient. rgba(80,180,230,…) for electric blue; rgba(255,160,40,…) for amber glow. |
| Halo radius (6 × h) | 6× effective radius | Gradient size multiplier. 4× = tight, punctual halo; 10× = wide, vaporous aura around the core. |
FAQ
rgba(180,230,80,…) and the core as rgba(200,250,100,…). Replace both with your RGB values — for example rgba(80,180,230,…) and rgba(100,210,255,…) for blue fireflies, or rgba(255,160,40,…) and rgba(255,200,80,…) for amber.requestAnimationFrame when the tab is inactive (zero CPU cost). However, the effect has no IntersectionObserver — it keeps running if the section is scrolled out of view but the tab stays active. Add an IntersectionObserver on the canvas to stop the RAF off-screen.psFireflies via getElementById — only one instance is possible. For multiple instances, extract the logic into a function parameterized by the canvas id and call it once per component.