Atmosphere✨ Premium

Sparkler Light Painting

Canvas buffer faded each frame → persistent long-exposure trail. Your cursor becomes a sparkler: incandescent halo, jetting sparks, and an automatic parametric heart loop.

Canvas 2DLight PaintingLong ExposureParticules

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

Hero / Landing — Dark-theme product launch — Sparkler Light Painting example 1

① Hero / Landing — Dark-theme product launch

WhenLaunch page or SaaS homepage in dark theme, above the fold — the effect starts automatically tracing the heart then the star.
WhyThe persistent light trail on a near-black background evokes a nighttime reveal or fireworks: it draws the eye without degrading headline readability (a side scrim is mandatory for text contrast).
SettingsSparklerPainting.setFade(0.008) for ~5 s persistence (full shape preserved); SparklerPainting.setSparks(8) for a denser spark shower. Side gradient scrim at 90→0% opacity left-to-right to clear the title area.
Product component — Year-end greeting card or notification — Sparkler Light Painting example 2

② Product component — Year-end greeting card or notification

WhenFull-width card or modal sent to active users in December — the canvas fills the card background, text overlays it via z-index.
WhyThe light trail and golden sparks reinforce the celebratory mood without cluttering an already information-rich card.
SettingsSparklerPainting.setFade(0.020) (short ~1.5 s trail, more understated); SparklerPainting.setSparks(3) for 3 sparks/frame. Scene background via .spk-scene { background: #08060e } to add a violet tint.
Interactive announcement — Writing your message in light — Sparkler Light Painting example 3

③ Interactive announcement — Writing your message in light

WhenA digital invitation or personalised e-card: the user traces their message or signature directly on the canvas with mouse or finger, against a dark paper-like surface.
WhyThe sparkler is literally a luminous writing tool: the persistent ~3 s trail leaves time to form letters, the live head guides every stroke, and the dark background dramatises each gesture. The automatic heart trace serves as the opening invitation — a cue for the user to take over.
SettingsSparklerPainting.setFade(0.006) for ~6 s persistence (letters stay readable long enough to finish a word); SparklerPainting.setSparks(3) for moderate sparks while writing. Set RESUME_MS = 999999 in source to disable auto-trace and leave the canvas blank, waiting for the user's first stroke.

How it works

The effect uses two stacked canvases: an off-screen accumulation buffer (this.acc) that receives trail strokes, and the display canvas (this.cv). On every frame the buffer is faded by a fillRect in destination-out mode with globalAlpha = 0.012 — this rate yields roughly 3 seconds of persistence, equivalent to a long-exposure photograph.

Each trail segment is painted to the buffer in three passes: a wide orange glow (lw=14, α=0.10), a mid amber stroke (lw=5, α=0.35), and a near-opaque white core thread (lw=1.5, α=0.90). globalCompositeOperation = 'lighter' causes overlapping zones to bloom toward white.

The live head (contact point) is a pre-computed off-screen sprite: a radial gradient from white through amber to transparent, drawn at 20 px, never written to the buffer. It is composited onto cv each frame with a sinusoidal pulse and three flickering rays at rotating angles. Sparks form a pool of up to 100 particles: random radial velocity, gravity +0.055/frame, colour (yellow→orange) indexed to remaining life — also on cv only.

When idle, an automatic mode traverses two shapes in turn (parametric heart, then 5-point star) via arc-length tables at N_SAMPLES = 300 — guaranteeing constant traversal speed regardless of local curvature. If the pointer stays still for more than 2.5 s (RESUME_MS), auto mode resumes. An IntersectionObserver cuts the rAF when the scene scrolls off-screen: zero wasted CPU.

Accessibility

  • prefers-reduced-motion: checked once at init via matchMedia. If active, drawStatic() draws the heart in a single pass (same 3 stroke layers, no rAF). No animation runs. Caveat: the check is not a live listener — an OS change mid-session takes effect only on page reload.
  • The <canvas> carries aria-hidden="true" — canvas content is not exposed to screen readers.
  • The .spk-scene container carries role="img" and an aria-label describing the effect in plain language.
  • The '✕ Clear' button has an explicit aria-label and an orange (#fb923c) focus-visible ring meeting WCAG AA. It is hidden in CSS when prefers-reduced-motion: reduce is active.
  • The light trail is driven by mouse or touch only — no keyboard path triggers it. Overlay text (headings, CTAs) remains in the normal tab order.

Browser compatibility

Requires Canvas 2D Context, requestAnimationFrame, and 'lighter' / 'destination-out' composite modes — supported across all modern browsers since 2016.

Chrome 110+✓ Full
Firefox 110+✓ Full
Safari 16+✓ Full
Edge 110+✓ Full
Mobile iOS✓ Touch OK (passive: false)
Android Chrome✓ Full

If <code>canvas</code> is unsupported (IE 8−), the <code>.spk-scene</code> container renders with a dark background (#050510) — no JS errors, the page stays functional. On browsers without <code>IntersectionObserver</code> (Safari pre-12.1), the rAF runs continuously.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="spk-scene" role="img" aria-label="Sparkler drawing a persistent light trail — automatic parametric heart trace, interactive with cursor">
  <canvas class="spk-canvas" aria-hidden="true"></canvas>

  <!-- Optional: clear button -->
  <div class="spk-ui">
    <button class="spk-clear-btn" type="button" aria-label="Clear the light trail">✕ Clear</button>
  </div>

  <!-- Optional: scrim + overlay content -->
  <div class="spk-scrim" aria-hidden="true"></div>
  <div class="spk-hero-content">
    <p class="spk-hero-eyebrow">Eyebrow</p>
    <h1 class="spk-hero-title">Title</h1>
    <p class="spk-hero-sub">Subtitle</p>
    <button class="spk-btn">CTA</button>
  </div>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
SparklerPainting.setFade(v) 0.012 Buffer fade rate per frame. 0.006 = ~6 s trail (long exposure), 0.025 = ~1 s trail (quick flash). Hot-swappable without reload.
SparklerPainting.setSparks(v) 5 Sparks emitted per frame during pointer or auto-trace movement. 1 = subtle, 12 = dense shower. Hot-swappable.
SparklerPainting.clear() Clears the accumulation buffer and resets sparks and auto-trace position — use it to start a fresh sequence on a business event.
GLOW_R (JS const, line 6) 10 Live-head sprite radius in canvas pixels (displayed at ~20 px). Raise to 18–22 for a larger, more imposing halo.
SPARKS_IDLE (JS const, line 11) 1 Sparks emitted per frame when the pointer is still. 0 = sparks only while moving; 3 = permanent ember glow.
RESUME_MS (JS const, line 10) 2500 Delay (ms) before auto-trace resumes after pointer stillness. 0 = instant resume; 5000 = leave full control to the user.
.spk-scene { background } #050510 Scene background colour, repainted by ctx.fillStyle before each composite. A deep blue (#030820) shifts the orange trail toward red.

FAQ

The constant STILL_MS = 80 sets the threshold: if the last mousemove is older than 80 ms, the pointer is considered still. The code keeps rendering the live head and emits one spark per frame (SPARKS_IDLE = 1), but adds no new segment to the buffer. After RESUME_MS = 2500 ms of stillness, automatic mode resumes.
Yes. Each Scene instance has an autoIdx property (0 = heart, 1 = star). In the source, at the line this.autoIdx = 1-this.autoIdx inside the 'pausing' block, replace it with this.autoIdx = 0 to loop the heart indefinitely. The public SparklerPainting API does not expose this — it requires editing the JS source.
Yes. touchmove events are listened with { passive: false } to allow preventDefault() and suppress scroll interference. On iOS, add touch-action: none to .spk-scene in CSS if a long press triggers the native context menu.