Parent : [[INDEX-WEB-UIUX]]
Web — Performance canvas/vidéo : adaptive quality, IntersectionObserver, RAF batching
Pattern 1 — Canvas animé : détecter les capacités device et adapter
Problème : Un canvas animé complexe freeze sur mobile et Firefox.
Solution : Détecter isMobileDevice(), isLowEndDevice(), et navigator.userAgent pour Firefox. Sur mobile/low-end → fallback CSS gradient statique. Sur Firefox → skip canvas. Ne jamais rendre le canvas avant que le container ait une taille non-nulle.
if (isMobile || isLowEnd) {
return <div style={{ background: 'linear-gradient(40deg, #02defc, #7ffe00, #fe9efb)' }} />
}
Pattern 2 — Pauser le canvas quand hors viewport (IntersectionObserver)
Problème : Une animation canvas qui tourne hors viewport consomme CPU inutilement.
Solution : Observer le container avec IntersectionObserver (threshold: 0.1). Setter un state isInView. Dans la boucle RAF : si !isInView → skip le calcul des particules mais maintenir le requestAnimationFrame pour ne pas casser la boucle.
const observer = new IntersectionObserver(
entries => entries.forEach(e => setIsInView(e.isIntersecting)),
{ threshold: 0.1 }
)
observer.observe(containerRef.current)
Pattern 3 — Éviter les forced reflows dans les handlers resize/scroll
Problème : Lire et écrire le DOM dans le même tick (forced reflow) bloque le thread principal.
Solution : Batching via requestAnimationFrame : grouper tous les getBoundingClientRect() (lectures DOM) ensemble, puis tous les writes ensemble dans la même RAF callback. Throttler le resize handler à 250ms.
const throttledResize = throttle(resize, 250)
// Dans resize :
requestAnimationFrame(() => {
const rect = container.getBoundingClientRect() // lecture groupée
canvas.width = rect.width * dpr // écriture groupée
})
Pattern 4 — Float32Array pour les particules
Problème : Stocker les propriétés de particules dans des objets JS génère une GC pressure élevée sur chaque frame.
Solution : Utiliser Float32Array typé avec un stride fixe (PARTICLE_PROPS par particule). Accès direct par index, zéro allocation sur chaque frame.
const PARTICLE_PROPS = 8
particles = new Float32Array(PARTICLE_COUNT * PARTICLE_PROPS)
// x=i, y=i+1, velX=i+2, velY=i+3, life=i+4, ttl=i+5, size=i+6, hue=i+7
Pattern 5 — Video ultra-optimisée : IntersectionObserver + connection detection
Problème : Charger une vidéo 4K 500MB au page load freeze complètement sur mobile ou connexion lente.
Solution : preload="metadata" par défaut. Charger la source uniquement quand isInView (IntersectionObserver). Détecter navigator.connection.effectiveType et navigator.connection.downlink pour downgrader la qualité automatiquement. Pauser quand hors viewport avec pauseWhenHidden.
// Ne charger la vidéo que quand visible
if (shouldLoad && isInView) {
videoRef.current.src = effectiveQuality === 'low' ? srcLow : srcMp4
}
Pattern 6 — DPR (Device Pixel Ratio) plafonné à 2
Problème : Sur les écrans retina haute densité (DPR 3+), le canvas est 9x plus grand que nécessaire.
Solution : const dpr = Math.min(window.devicePixelRatio || 1, 2) — plafonner à 2 maximum. Appliquer ctx.scale(dpr, dpr) après resize pour que le canvas soit sharp sans être surdimensionné.