Back

Comment ajouter un effet de chute de neige simple à votre site web

Comment ajouter un effet de chute de neige simple à votre site web

Une animation saisonnière sur un site web peut ravir les visiteurs, mais la plupart des tutoriels ignorent ce qui compte en production : les performances, l’accessibilité et l’expérience utilisateur. Vous ne voulez pas qu’une animation décorative en arrière-plan dégrade vos Core Web Vitals ou agace les utilisateurs qui préfèrent un mouvement réduit.

Ce guide vous montre comment créer un effet de chute de neige léger en canvas qui respecte les préférences des utilisateurs, se met en pause lorsqu’il est invisible et reste discret. Vous apprendrez également quand un effet de neige CSS plus simple est pertinent.

Points clés à retenir

  • Le canvas est plus fiable que les approches basées sur le DOM dès que vous dépassez une petite poignée de particules
  • Respectez toujours prefers-reduced-motion pour honorer les préférences d’accessibilité des utilisateurs
  • Utilisez l’API Page Visibility pour mettre en pause les animations dans les onglets en arrière-plan et économiser des ressources
  • La neige en CSS pur fonctionne pour des implémentations minimales (5-10 flocons) mais ne passe pas à l’échelle

Pourquoi utiliser le canvas pour une animation de chute de neige en JavaScript

Les approches basées sur le DOM créent des éléments individuels pour chaque flocon de neige. Cela fonctionne pour une poignée de particules, mais passer à des dizaines ou des centaines signifie une manipulation constante du DOM, des recalculs de mise en page et une pression mémoire due à la création et suppression d’éléments.

Un effet de chute de neige en canvas dessine tout sur un seul élément. Vous contrôlez la boucle de rendu, gérez l’état des particules dans de simples tableaux et évitez complètement la surcharge du DOM. Dès que vous dépassez un petit nombre de particules ou souhaitez un mouvement plus fluide, le canvas devient le choix par défaut le plus prévisible.

Compromis à comprendre :

  • Le canvas nécessite JavaScript — pas de JS signifie pas de neige
  • Le texte à l’intérieur d’un canvas n’est pas accessible aux lecteurs d’écran (acceptable pour des effets purement décoratifs)
  • Les animations CSS ne sont pas « gratuites » — elles consomment toujours des ressources CPU/GPU

Configuration de l’élément canvas

Positionnez le canvas derrière votre contenu avec du CSS :

#snowfall {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: -1;
}

La règle pointer-events: none garantit que le canvas ne bloque jamais le défilement, les clics ou toute interaction utilisateur.

<canvas id="snowfall" aria-hidden="true"></canvas>

L’ajout de aria-hidden="true" indique aux technologies d’assistance d’ignorer cet élément purement décoratif.

Construction de la boucle d’animation

Voici une implémentation minimale avec les garde-fous appropriés :

const canvas = document.getElementById('snowfall');
const ctx = canvas.getContext('2d');
let flakes = [];
let animationId = null;

function resize() {
  const dpr = window.devicePixelRatio || 1;
  canvas.width = window.innerWidth * dpr;
  canvas.height = window.innerHeight * dpr;
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.scale(dpr, dpr);
}

function createFlake() {
  return {
    x: Math.random() * window.innerWidth,
    y: -10,
    radius: Math.random() * 3 + 1,
    speed: Math.random() * 1 + 0.5,
    opacity: Math.random() * 0.6 + 0.4
  };
}

function update() {
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
  
  if (flakes.length < 80 && Math.random() > 0.95) {
    flakes.push(createFlake());
  }
  
  flakes = flakes.filter(f => {
    f.y += f.speed;
    ctx.beginPath();
    ctx.arc(f.x, f.y, f.radius, 0, Math.PI * 2);
    ctx.fillStyle = `rgba(255, 255, 255, ${f.opacity})`;
    ctx.fill();
    return f.y < window.innerHeight + 10;
  });
  
  animationId = requestAnimationFrame(update);
}

resize();
window.addEventListener('resize', resize);

Cela gère correctement les écrans haute résolution en mettant à l’échelle le buffer du canvas pour correspondre au devicePixelRatio. L’appel ctx.setTransform(1, 0, 0, 1, 0, 0) réinitialise la matrice de transformation avant d’appliquer la nouvelle échelle, empêchant une mise à l’échelle cumulative lors du redimensionnement de la fenêtre.

Garde-fous essentiels pour les performances et l’accessibilité

Respect des préférences utilisateur

Les utilisateurs qui activent prefers-reduced-motion ont explicitement demandé moins d’animations. Respectez cela :

const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
let prefersReduced = reducedMotionQuery.matches;

reducedMotionQuery.addEventListener('change', (e) => {
  prefersReduced = e.matches;
  if (prefersReduced) {
    cancelAnimationFrame(animationId);
    ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
  } else if (!document.hidden) {
    update();
  }
});

if (!prefersReduced) {
  update();
}

Cette implémentation écoute les changements de préférence de mouvement de l’utilisateur, permettant à l’animation de réagir dynamiquement si le paramètre change pendant que la page est ouverte.

Mise en pause lorsque masqué

Exécuter des animations dans des onglets en arrière-plan gaspille la batterie et le CPU. L’API Page Visibility résout ce problème :

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    cancelAnimationFrame(animationId);
  } else if (!prefersReduced) {
    update();
  }
});

Quand les effets de neige en CSS ont du sens

Pour de très petites implémentations — peut-être 5 à 10 flocons sur une section hero — une approche CSS pure évite complètement JavaScript :

.snowflake {
  position: absolute;
  color: white;
  animation: fall 8s linear infinite;
}

@keyframes fall {
  to { transform: translateY(100vh); }
}

Cela fonctionne pour des cas d’usage décoratifs minimaux mais ne passe pas à l’échelle. Chaque élément déclenche toujours du compositing, et vous perdez le contrôle programmatique sur la densité et le comportement.

Options de personnalisation

Ajustez ces valeurs pour correspondre à l’esthétique de votre site :

  • Densité : Modifiez le plafond de 80 et le seuil d’apparition de 0.95
  • Plage de vitesse : Modifiez le calcul Math.random() * 1 + 0.5
  • Taille : Ajustez le calcul du rayon
  • Portée : Ciblez un conteneur spécifique au lieu de window.innerWidth/Height

Conclusion

Une animation de site web festive devrait enrichir l’expérience sans la compromettre. Utilisez le canvas pour des performances évolutives, respectez prefers-reduced-motion, mettez en pause lorsque la page n’est pas visible et gardez l’effet non-interactif. Commencez avec des nombres de particules conservateurs et ajustez en fonction de tests réels sur appareils — pas d’hypothèses sur ce que les navigateurs peuvent gérer.

FAQ

Une animation canvas bien implémentée a un impact minimal sur les Core Web Vitals. Puisque le canvas effectue le rendu sur un seul élément et utilise requestAnimationFrame, il ne causera pas de décalages de mise en page ni ne bloquera le thread principal. Maintenez des nombres de particules raisonnables (moins de 100) et mettez en pause l'animation lorsque l'onglet est masqué pour maintenir de bons scores de performance.

Oui. Ajoutez une propriété de dérive à chaque objet flocon et mettez à jour la position x dans votre boucle d'animation en même temps que la position y. Utilisez Math.sin avec un décalage temporel pour une oscillation d'apparence naturelle, ou appliquez une valeur horizontale constante pour un vent régulier. Randomisez les valeurs de dérive par flocon pour de la variété.

Remplacez window.innerWidth et window.innerHeight par les dimensions de votre conteneur cible. Utilisez getBoundingClientRect pour obtenir la taille et la position du conteneur. Changez le CSS du canvas de position fixed à position absolute et placez-le à l'intérieur de votre élément conteneur cible.

Cela se produit lorsque la taille du buffer du canvas ne correspond pas à la densité de pixels de l'écran. La mise à l'échelle devicePixelRatio dans l'exemple de code corrige cela en créant un buffer canvas plus grand et en mettant à l'échelle le contexte de dessin. Assurez-vous d'appliquer cette mise à l'échelle dans votre fonction resize et de réinitialiser la matrice de transformation avant chaque opération de mise à l'échelle.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay