Sparkler Light Painting
Buffer canvas estompé chaque frame → traînée pose longue persistante. Le curseur devient un cierge magique : halo orangé, étincelles, cœur paramétrique auto.
Vous aurez accès à la démo interactive avec un compte gratuit.
Cet effet fait partie d'Effect.Labs — 811 effets vanilla, une partie gratuite, une partie premium. Atmosphère comprend 57 effets, dont 10 gratuits. Découvrir la catégorie →
3 exemples d'usage



Comment ça marche
L'effet repose sur deux canvas superposés : un buffer d'accumulation hors-écran (this.acc) qui reçoit les traits, et le canvas d'affichage (this.cv). À chaque frame, le buffer est estompé par un fillRect en mode destination-out avec globalAlpha = 0.012 — ce taux produit une persistance d'environ 3 secondes, l'équivalent d'une pose longue photographique.
Le tracé de traînée s'opère en trois passes dans le buffer : une ligne large orange basse opacité (lw=14, α=0,10) pour le halo chaud, une ligne amber intermédiaire (lw=5, α=0,35), et un filet blanc quasi-opaque (lw=1,5, α=0,90) pour le fil incandescent central. Le mode globalCompositeOperation = 'lighter' fait briller les zones superposées — les croisements de tracés s'accumulent vers le blanc.
La tête vive (point de contact) est un sprite off-screen pré-calculé : dégradé radial blanc → ambre → orange → transparent, redimensionné à 20 px, jamais écrit dans le buffer. Elle est composée sur cv à chaque frame avec une pulsation sinusoïdale et trois rayons scintillants aux angles rotatifs. Les étincelles forment un pool de 100 particules max : vélocité radiale aléatoire, gravité +0,055/frame, couleur indexée sur la durée de vie (jaune → orange) — également sur cv uniquement.
Sans interaction, un mode automatique parcourt deux formes en alternance (cœur paramétrique, puis étoile à 5 branches) via des tables arc-longueur à N_SAMPLES = 300 points — garantissant une vitesse de parcours constante quelle que soit la courbure locale. Si le pointeur reste immobile plus de 2,5 s (RESUME_MS), l'auto reprend. Un IntersectionObserver coupe le rAF dès que la scène sort du viewport : zéro CPU inutile.
Accessibilité
- prefers-reduced-motion : vérifié une fois à l'initialisation via
matchMedia. Si activé,drawStatic()trace le cœur en une seule passe (mêmes 3 couches de trait, sans rAF). Aucune animation ne tourne. Limite : la vérification n'est pas réactive en direct — un changement OS en cours de session ne prend effet qu'au rechargement. - Le
<canvas>portearia-hidden="true"— le contenu canvas n'est pas exposé aux lecteurs d'écran. - Le conteneur
.spk-sceneporterole="img"et unaria-labeldécrivant l'effet en langage naturel. - Le bouton « ✕ Effacer » a un
aria-labelexplicite et un anneaufocus-visibleorange (#fb923c) conforme WCAG AA. Il est masqué en CSS quandprefers-reduced-motion: reduceest actif. - La traînée lumineuse n'est accessible qu'à la souris ou au toucher — aucun parcours clavier ne la déclenche. Les textes overlay (titres, CTA) restent dans le flux de tabulation normal.
Compatibilité navigateur
Nécessite Canvas 2D Context, requestAnimationFrame et les modes de composition 'lighter' / 'destination-out' — supportés dans tous les navigateurs modernes depuis 2016.
Si <code>canvas</code> n'est pas supporté (IE 8−), le conteneur <code>.spk-scene</code> s'affiche avec son fond sombre (#050510) — aucune erreur JS, la page reste fonctionnelle. Sur les navigateurs sans <code>IntersectionObserver</code> (vieux Safari pré-12.1), le rAF tourne en continu.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
<div class="spk-scene" role="img" aria-label="Cierge magique dessinant une lumière persistante — tracé automatique de cœur lumineux, interactif au curseur">
<canvas class="spk-canvas" aria-hidden="true"></canvas>
<!-- Facultatif : bouton Effacer -->
<div class="spk-ui">
<button class="spk-clear-btn" type="button" aria-label="Effacer le tracé lumineux">✕ Effacer</button>
</div>
<!-- Facultatif : scrim + contenu overlay -->
<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">Titre</h1>
<p class="spk-hero-sub">Sous-titre</p>
<button class="spk-btn">CTA</button>
</div>
</div>
HTML + CSS + JS complet, prêt à coller — avec des centaines d'effets premium.
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
| SparklerPainting.setFade(v) | 0.012 | Taux d'estompage du buffer par frame. 0.006 = traînée ~6 s (pose longue), 0.025 = traînée ~1 s (flash rapide). Modifiable à chaud sans rechargement. |
| SparklerPainting.setSparks(v) | 5 | Étincelles émises par frame en déplacement du pointeur ou du tracé auto. 1 = sobre, 12 = pluie dense. Modifiable à chaud. |
| SparklerPainting.clear() | — | Efface le buffer d'accumulation et réinitialise les étincelles et la position de tracé automatique — utile pour démarrer une nouvelle séquence sur un événement métier. |
| GLOW_R (const JS, ligne 6) | 10 | Rayon du sprite de tête vive en pixels canvas (affiché à ~20 px). Augmenter à 18–22 pour un halo plus imposant. |
| SPARKS_IDLE (const JS, ligne 11) | 1 | Étincelles émises par frame quand le pointeur est immobile. 0 = étincelles uniquement en mouvement ; 3 = braise permanente. |
| RESUME_MS (const JS, ligne 10) | 2500 | Délai (ms) avant reprise du tracé automatique après immobilité du pointeur. 0 = reprise immédiate ; 5000 = laisser l'utilisateur en pleine maîtrise. |
| .spk-scene { background } | #050510 | Couleur de fond de la scène, recalquée par ctx.fillStyle avant chaque composite. Un bleu profond (#030820) pousse la traînée orangée vers le rouge. |
FAQ
STILL_MS = 80 fixe le seuil : si le dernier mousemove date de plus de 80 ms, le pointeur est considéré immobile. Le code continue d'afficher la tête vive et émet une étincelle par frame (SPARKS_IDLE = 1), mais n'ajoute plus de segment au buffer. Après RESUME_MS = 2500 ms d'immobilité, le mode automatique reprend.Scene a une propriété autoIdx (0 = cœur, 1 = étoile). Dans le source, à la ligne this.autoIdx = 1-this.autoIdx du bloc 'pausing', remplacez par this.autoIdx = 0 pour boucler indéfiniment sur le cœur. L'API publique SparklerPainting ne l'expose pas — la modification est dans le JS source.touchmove sont écoutés avec { passive: false } pour permettre preventDefault() et supprimer le scroll parasite. Sur iOS, ajoutez touch-action: none à .spk-scene si le toucher de plus d'une seconde déclenche le menu contextuel natif.