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-motionpour 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.
Discover how at OpenReplay.com.
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
80et le seuil d’apparition de0.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.