Parent : [[INDEX-WEB-UIUX]]
Web UI/UX - IntersectionObserver Lazy Loading : thumbnails, rootMargin 200px, Map cache
Problème
Charger 50 images d'un coup crée un LCP catastrophique et une expérience laggy. loading="lazy" natif ne suffit pas pour des images générées dynamiquement (scrape CFX forums).
Solution
IntersectionObserver avec rootMargin: '200px' (chargement anticipé), combiné avec un Map cache global pour éviter les requêtes répétées.
const imgCache = new Map<string, string>()
function useLazyThumbnailWithRef(asset: AssetResult) {
const ref = useRef<HTMLDivElement>(null)
const [resolved, setResolved] = useState<string | undefined>(asset.thumbnailUrl)
useEffect(() => {
if (asset.thumbnailUrl) { setResolved(asset.thumbnailUrl); return }
if (asset.source !== 'cfx') return
const cached = imgCache.get(asset.url)
if (cached) { setResolved(cached); return }
const el = ref.current
if (!el) return
const observer = new IntersectionObserver(
(entries) => {
if (!entries[0].isIntersecting) return
observer.disconnect()
fetch(`/api/thumbnail?url=${encodeURIComponent(asset.url)}`)
.then((r) => r.json())
.then((data: { images?: string[] }) => {
const first = data.images?.[0]
if (first) { imgCache.set(asset.url, first); setResolved(first) }
})
.catch(() => {})
},
{ rootMargin: '200px' } // Charge 200px AVANT d'entrer dans le viewport
)
observer.observe(el)
return () => observer.disconnect()
}, [asset.url, asset.thumbnailUrl, asset.source])
return { ref, thumbnail: resolved }
}
Points clés
rootMargin: '200px': l'image commence à charger 200px avant d'être visible → pas de flash blancMapglobalimgCache: évite de rescraper la même URL si l'utilisateur scrolle up/downobserver.disconnect()après le premier trigger : évite les requêtes multiples- Fallback icon si pas d'image :
TYPE_ICON[asset.type]affiché dans un placeholder loading="lazy"+decoding="async"sur l'img natif : double protection
Pourquoi
- LCP amélioré : seules les images visibles sont chargées
- Pas de requêtes dupliquées grâce au cache Map
rootMargincréé une expérience fluide sans flash de chargement
Quand l'utiliser
- Grids de cartes avec images (e-commerce, galleries, dashboards)
- Images scrapées dynamiquement (forums, API externes)
Quand NE PAS l'utiliser
- Hero image (charger immédiatement avec
priority) - Moins de 10 images sur la page (overkill)
Références
- ProjectAlpha/AssetCard.tsx L25-60