Créer une section about React avec lecteur vidéo en modale
Cette section about React affiche une miniature avec un bouton play animé ; un clic ouvre une modale iframe avec une entrée scale+fade pilotée par AnimatePresence de Framer Motion. Les statistiques sous la miniature s'animent en cascade avec whileInView.
- Stack : React 18, Framer Motion 11, Lucide React (icônes Play + X), Tailwind v4, ~175 lignes.
- État : un seul booléen `isPlaying` contrôle la modale ; aucune bibliothèque d'état externe nécessaire.
- Accessible : le bouton fermer porte un aria-label ; l'iframe a un attribut title ; cliquer le backdrop ferme aussi la modale.
- Responsive : la miniature est en aspect-video (16:9) avec overflow-hidden ; la modale est limitée à max-w-4xl et conserve son ratio sur tout écran.
- Thème : toutes les couleurs viennent de propriétés CSS personnalisées (--color-background-dark, --color-accent, etc.), aucune valeur codée en dur.
About Video est une section centrée qui s'ouvre sur un titre et un badge, puis présente une miniature vidéo pleine largeur avec un bouton play bien visible. Un clic sur la miniature ouvre une modale lightbox par-dessus la page. Une rangée de compteurs de stats sous la vidéo, chacun animé à l'entrée dans le viewport, complète la preuve sociale sans avoir besoin d'une section séparée.
Anatomie
La section se divise en trois zones verticales. En haut, un bloc texte contraint à max-w-2xl contient le badge optionnel (forme pilule avec bordure et couleur d'accent), le titre h2 et un paragraphe de sous-titre. En dessous, le bloc miniature occupe max-w-5xl en 16:9 avec des coins arrondis et un overlay semi-transparent ; l'overlay affiche toujours le bouton play, et le groupe-hover le noircit légèrement pendant que le bouton grossit. En bas, une grille 3 colonnes affiche les stats avec leurs grandes valeurs en couleur d'accent et leurs libellés discrets.
Comment ça marche
Chaque bloc visuel utilise `whileInView` de Framer Motion avec `viewport={{ once: true }}` pour n'animer qu'au premier passage dans le viewport. Le header texte monte en fondu (y: 20 → 0, opacité 0 → 1) en 500ms. La miniature entre avec un fondu légèrement plus long (600ms) et un délai de 100ms. Chaque stat reçoit un délai supplémentaire en cascade : `0.2 + i * 0.08`s. La modale passe par `AnimatePresence` pour que l'animation de sortie (opacité 0, scale 0.9) s'exécute vraiment avant que React démonte l'élément, sans `AnimatePresence`, la fermeture serait instantanée.
Comment le coder en React
Construis la miniature avec un overlay play
Rends un div positionné en relative à aspect-video. Pose l'image de fond via un style inline sur un div absolu inset-0 pour que l'image ne casse jamais le ratio 16:9. Empile un deuxième div absolu inset-0 avec bg-black/30 pour l'overlay ; ajoute une classe group sur le wrapper et transite l'overlay vers bg-black/40 au hover.
<motion.div className="relative mt-12 aspect-video rounded-[var(--radius-xl)] overflow-hidden cursor-pointer group" onClick={() => setIsPlaying(true)} > <div className="absolute inset-0 bg-cover bg-center" style={{ backgroundImage: `url(${thumbnailUrl})` }} /> <div className="absolute inset-0 flex items-center justify-center bg-black/30 group-hover:bg-black/40 transition-colors"> <div className="flex h-20 w-20 items-center justify-center rounded-full group-hover:scale-110 transition-transform duration-300" style={{ backgroundColor: "var(--color-accent)" }}> <Play className="h-8 w-8 ml-1" /> </div> </div> </motion.div>Ouvre et ferme la modale avec AnimatePresence
Enveloppe le JSX de la modale dans `<AnimatePresence>` et conditionne-le par `{isPlaying && videoUrl && ...}`. Donne au backdrop un motion.div avec entrée opacité 0 → 1 et sortie → 0. Le panneau interne passe de scale 0.9 → 1 à l'entrée et scale 0.9 à la sortie. Un clic sur le backdrop met isPlaying à false ; stopPropagation sur le panneau intérieur évite les fermetures accidentelles.
<AnimatePresence> {isPlaying && videoUrl && ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4" onClick={() => setIsPlaying(false)} > <motion.div initial={{ scale: 0.9 }} animate={{ scale: 1 }} exit={{ scale: 0.9 }} className="relative w-full max-w-4xl aspect-video" onClick={(e) => e.stopPropagation()} > <iframe src={videoUrl} allow="autoplay; fullscreen" allowFullScreen title="Video de presentation" className="w-full h-full rounded-[var(--radius-lg)]" /> </motion.div> </motion.div> )} </AnimatePresence>Fais entrer les stats en cascade avec whileInView
Parcoure le tableau stats et donne à chaque motion.div un délai croissant : `0.2 + index * 0.08`. Passe `viewport={{ once: true }}` pour que l'animation ne se rejoue pas au scroll vers le haut. La valeur en couleur d'accent est dans un grand p gras, le libellé dans un p sm discret en dessous.
{stats.map((stat, i) => ( <motion.div key={stat.label} initial={{ opacity: 0, y: 16 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: 0.2 + i * 0.08, duration: 0.4 }} className="text-center" > <p className="text-3xl font-bold" style={{ color: "var(--color-accent)" }}> {stat.value} </p> <p className="mt-1 text-sm" style={{ color: "var(--color-foreground-light)" }}> {stat.label} </p> </motion.div> ))}
Quand l'utiliser
À utiliser sur les pages entreprise ou produit où tu as une vidéo de présentation ou de démonstration à mettre en avant. Fonctionne bien comme section de confiance en milieu de landing page, après le hero et avant les tarifs. À éviter si tu n'as pas de vidéo pertinente, ou si la page comporte déjà une section média lourde à proximité. Sur les pages très axées conversion, l'interaction modale peut distraire ; dans ce cas, une vidéo inline en autoplay peut mieux servir.
Utilisé par
- Loom, Utilise une miniature vidéo hero avec un grand bouton play qui ouvre une démo produit dans une modale lightbox plein écran.
- Notion, Présente une section vidéo produit centrée avec une miniature, un overlay play et des stats clés sous le lecteur vidéo.
- Webflow, Positionne une vidéo de présentation produit avec overlay play sur ses pages marketing, s'ouvrant dans une modale.
FAQ
Pourquoi utiliser un iframe plutôt qu'un élément video HTML natif ?
Un iframe permet d'intégrer des vidéos hébergées (YouTube, Vimeo, Wistia) sans auto-héberger le fichier. Passe l'URL d'intégration avec les paramètres autoplay dans la query string. Pour des fichiers auto-hébergés, remplace l'iframe par un élément video avec `autoPlay` et `controls`.
Comment arrêter la vidéo quand la modale se ferme ?
Comme l'iframe est démonté quand `isPlaying` passe à false, le navigateur arrête la lecture automatiquement. Si tu passes à un élément video natif, appelle `videoRef.current.pause()` dans le handler de fermeture avant de changer l'état.
Peut-on utiliser cette section sans la rangée de stats ?
Oui. Le bloc stats est rendu conditionnellement (`stats && stats.length > 0`), donc passer un tableau vide ou omettre la prop le retire sans aucun effet de bord sur le layout.
La modale est-elle accessible au clavier ?
Le bouton fermer est un vrai élément button avec aria-label, donc il reçoit le focus et répond à Entrée/Espace. Pour un comportement trap-focus complet, ajoute une bibliothèque de piège de focus ou déplace manuellement le focus sur le bouton fermer à l'ouverture et restaure-le sur la miniature à la fermeture.