Atmosphère✨ Premium

Smoke

Un canvas 2D fait monter des particules de fumée circulaires semi-transparentes avec dérive sinusoïdale et expansion radiale — rAF pur, zéro dépendance.

JSCanvasSmoke

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

Hero / Landing — Ambiance dark-brand — Smoke exemple 1

① Hero / Landing — Ambiance dark-brand

QuandHero d'un site à identité sombre (spiritueux, parfumerie, fiction, gaming) où l'atmosphère visuelle prime sur l'information dense.
PourquoiLa fumée translucide monte sans interaction, crée une profondeur d'ambiance et n'écrase pas le texte — zéro image externe, zéro réseau, charge CPU minimale.
RéglagesRGB 160,120,80 pour une fumée ambrée ; decay: 0.002 + 0.002 * Math.random() pour des particules longue durée (200–500 frames) ; spawn centré sous l'objet source (bougie, encens, brûleur).
Composant produit — Carte de traitement asynchrone — Smoke exemple 2

② Composant produit — Carte de traitement asynchrone

QuandUne opération longue est en cours (export PDF, transcription, rendu vidéo) et la carte de statut doit signaler l'activité sans spinner classique.
PourquoiLa fumée sortant d'un icône « forge » ou « processeur » remplace le loader par une esthétique craft/industrie plus mémorable dans un dashboard technique.
Réglagesvx: 0.2 * (Math.random() - .5) pour un panache plus droit (±0,1 px) ; Math.random() > 0.6 pour densité réduite (40 % spawn) ; taille init 3 * Math.random() + 2 (2–5 px) pour une fumée fine et délicate.
Micro-interaction de conversion — Gate contenu premium — Smoke exemple 3

③ Micro-interaction de conversion — Gate contenu premium

QuandContenu verrouillé derrière un compte premium : le visiteur aperçoit une prévisualisation floue enveloppée de fumée dense et un CTA « Révéler ».
PourquoiLa fumée épaisse renforce le mystère du contenu caché et construit une tension visuelle qui valorise l'acte de déverrouillage.
RéglagesMath.random() > 0.1 (90 % spawn) pour haute densité ; vy: -0.8 * Math.random() - 0.3 pour montée lente et fumée stagnante ; opacité portée à 0.25 * a.life pour un voile nettement plus opaque.

Comment ça marche

L'IIFE initialise le canvas via setupCanvas("psSmoke") — la fonction lit le getBoundingClientRect() du parent pour fixer width/height, puis renvoie {canvas, ctx, w, h}. Un tableau e accumule les particules vivantes. La boucle requestAnimationFrame repeint chaque frame : clearRect puis fillRect en #0a0a0f effacent le frame précédent, puis toutes les particules sont mises à jour et dessinées avant les particules expirées retirées par splice en arrière.

À chaque frame, si Math.random() > 0.3 (70 % de probabilité), une particule naît près du centre bas — x = w/2 ± 5 px, y = h − 30. Ses propriétés : drift horizontal vx = ±0,25 px/frame, montée vy entre −0,5 et −2,0 px/frame, life = 1, decay entre 0,003 et 0,008 (durée de vie 125–333 frames), taille initiale 4–12 px.

Chaque frame, la position évolue : x += vx + 0,3 × sin(0,02 × y) — la composante sinusoïdale indexée sur Y produit une dérive ondulante progressive qui simule la turbulence naturelle de la fumée. y += vy (ascension), life -= decay, size += 0,15 (expansion radiale). Chaque particule est un arc() plein en rgba(180,180,190, 0,15 × life) : 15 % d'opacité max à la naissance, invisible à la mort.

Le code ne gère ni prefers-reduced-motion, ni IntersectionObserver, ni redimensionnement du canvas après l'init. L'IIFE cible un seul canvas par identifiant fixe (id="psSmoke") — pour plusieurs colonnes simultanées, la boucle particules doit être extraite dans une fonction paramétrable par id.

Accessibilité

  • prefers-reduced-motion absent : le code ne vérifie pas cette préférence OS — l'animation boucle indéfiniment quelle que soit la configuration accessibilité. Correction : ajouter if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; avant le lancement du rAF.
  • Le canvas ne porte ni aria-hidden="true" ni role dans le HTML source — à corriger en ajoutant aria-hidden="true" pour tout usage décoratif, afin d'exclure l'élément du flux d'accessibilité.
  • Aucun contrôle pause/arrêt n'est exposé — WCAG 2.2.2 impose un mécanisme de pause pour toute animation auto-lancée de plus de 5 secondes.
  • Pas d'interaction clavier par design (effet purement décoratif) — convient à un fond d'ambiance à condition que le canvas soit isolé du flux d'accessibilité via aria-hidden.
  • Faible opacité des particules (rgba(180,180,190) à 15 % max) — la fumée ne masque pas le contenu textuel superposé sur fond sombre, ce qui limite la gêne visuelle pour les utilisateurs sensibles au contraste.

Compatibilité navigateur

Nécessite Canvas 2D Context et requestAnimationFrame — supportés dans tous les navigateurs modernes. Zéro dépendance externe.

Chrome 88+✓ Complet
Firefox 87+✓ Complet
Safari 15+✓ Complet
Edge 88+✓ Complet
Mobile iOS✓ Tactile OK
Android Chrome✓ Complet

Sans support Canvas, le conteneur s'affiche vide sur fond sombre — aucune erreur JS fatale, le contenu textuel superposé reste lisible.

Le code

Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :

index.html — structure
<div class="demo-preview">
  <div class="ps-canvas-wrap">
    <canvas class="ps-canvas" id="psSmoke" width="392" height="280"
            aria-hidden="true"></canvas>
  </div>
</div>
🔒 Débloquer le code complet — à partir de 2,99 € le 1er mois

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éfautEffet
rgba(180,180,190, ...) gris-bleu Valeurs RGB dans ctx.fillStyle — passez 160,120,80 pour fumée ambrée, 100,130,180 pour fumée bleue, 200,60,40 pour vapeur volcanique rougeâtre.
0.15 * a.life 0.15 Multiplicateur d'opacité max — augmentez à 0,35 pour une fumée dense et visible, réduisez à 0,06 pour des volutes à peine perceptibles en arrière-plan.
Math.random() > .3 0.3 (70 % spawn) Seuil de spawn par frame — montez à 0,8 (20 %) pour une fumée éparse et distante, descendez à 0,05 (95 %) pour une colonne dense et continue.
vy: 1.5 * -Math.random() - .5 −0,5 à −2,0 px/frame Vitesse de montée — multipliez le coefficient 1,5 par 0,4 pour une fumée paresseuse, par 2,5 pour une vapeur vive.
.3 * Math.sin(.02 * a.y) amplitude 0,3 px Amplitude de la dérive sinusoïdale — mettez à 0 pour un panache vertical droit, à 1,2 pour une fumée très ondulée et serpentine.
size: 8 * Math.random() + 4 4–12 px Taille initiale des particules — utilisez 20 * Math.random() + 10 pour de gros nuages opulents, 3 * Math.random() + 1 pour des volutes fines et précises.
size += .15 0,15 px/frame Taux d'expansion radiale — augmentez à 0,4 pour une fumée qui s'évase rapidement, mettez à 0 pour des particules à taille fixe (style brume flottante).

FAQ

Le code source cible un seul canvas via getElementById("psSmoke") — l'IIFE ne peut s'exécuter qu'une fois. Pour plusieurs colonnes, extrayez la boucle particules dans une fonction prenant un id en paramètre et appelez-la pour chaque canvas. La logique interne est entièrement indépendante : chaque appel maintient son propre tableau de particules et son propre rAF.
Ajoutez avant le lancement du requestAnimationFrame : if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;. Pour un comportement dégradé (une frame statique), dessinez les premières particules sans démarrer la boucle. Depuis Chrome 76+, le MediaQueryList émet un événement change — vous pouvez réagir en direct si l'utilisateur modifie la préférence pendant la session.
Remplacez la ligne fillStyle = `rgba(180,180,190,${i})` par fillStyle = `hsla(${200 + (a.life * 20) | 0},20%,75%,${i})` — la particule naît froide (HSL 220, bleuté) et vire vers le gris neutre (HSL 200) en vieillissant, simulant un refroidissement progressif en montant.