Cómo crear una sección about con lightbox de vídeo en React
Esta sección about React muestra una miniatura con un botón play animado; al hacer clic se abre un modal basado en iframe con una entrada scale+fade impulsada por AnimatePresence de Framer Motion. Las estadísticas bajo la miniatura aparecen una a una con una animación escalonada whileInView.
- Stack: React 18, Framer Motion 11, Lucide React (iconos Play + X), Tailwind v4, ~175 líneas.
- Estado: un único booleano `isPlaying` alterna el modal; no se necesita ninguna librería de estado externa.
- Accesible: el botón de cerrar lleva un aria-label; el iframe tiene un atributo title; hacer clic en el backdrop también cierra el modal.
- Responsive: la miniatura usa aspect-video (16:9) con overflow-hidden; el modal se limita a max-w-4xl y conserva su ratio en cualquier pantalla.
- Theming: todos los colores provienen de propiedades CSS personalizadas (--color-background-dark, --color-accent, etc.), sin valores codificados a fuego.
About Video es una sección centrada que arranca con un titular y un badge, y luego muestra una miniatura de vídeo a todo el ancho con un botón play bien visible. Al hacer clic en la miniatura se abre un modal lightbox sobre la página. Una fila de contadores de estadísticas bajo el vídeo, cada uno animado al entrar en el viewport, completa la prueba social sin necesidad de una sección aparte.
Anatomía
La sección se divide en tres zonas verticales. Arriba, un bloque de texto limitado a max-w-2xl contiene el badge opcional (forma de píldora con borde y color de acento), el título h2 y un párrafo de subtítulo. Debajo, el bloque de miniatura ocupa max-w-5xl en 16:9 con esquinas redondeadas y un overlay semitransparente; el overlay muestra siempre el botón play, y el group-hover lo oscurece ligeramente mientras el botón crece. Abajo, una rejilla de 3 columnas muestra las estadísticas con sus grandes valores en color de acento y sus etiquetas discretas.
Cómo funciona
Cada bloque visual usa `whileInView` de Framer Motion con `viewport={{ once: true }}` para animarse solo la primera vez que entra en el viewport. El header de texto sube con un fundido (y: 20 a 0, opacidad 0 a 1) en 500ms. La miniatura entra con un fundido algo más largo de 600ms y un retardo de 100ms. Cada estadística recibe un retardo escalonado adicional: `0.2 + i * 0.08`s, de modo que aparecen en cascada de forma natural. El modal usa `AnimatePresence` para que la animación de salida (opacidad 0, scale 0.9) se ejecute realmente antes de que React desmonte el elemento; sin `AnimatePresence`, la salida sería instantánea.
Cómo crearlo en React
Construye la miniatura con un overlay de play
Renderiza un div con posición relative en aspect-video. Coloca la imagen de fondo mediante un style inline en un div absolute inset-0 para que la imagen nunca rompa el ratio 16:9. Apila un segundo div absolute inset-0 con bg-black/30 para el overlay; añade una clase group en el wrapper y haz que el overlay transicione a bg-black/40 al 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>Abre y cierra el modal con AnimatePresence
Envuelve el JSX del modal en `<AnimatePresence>` y condiciónalo con `{isPlaying && videoUrl && ...}`. Da al backdrop un motion.div con entrada de opacidad 0 a 1 y salida a 0. El panel interior pasa de scale 0.9 a 1 al entrar y a scale 0.9 al salir. Un clic en el backdrop pone isPlaying a false; stopPropagation en el panel interior evita los cierres accidentales.
<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>Escalona los contadores de estadísticas con whileInView
Recorre el array stats y da a cada motion.div un retardo creciente: `0.2 + index * 0.08`. Pasa `viewport={{ once: true }}` para que la animación no se repita al hacer scroll hacia arriba. El valor en color de acento va en un p grande en negrita, y la etiqueta en un p sm discreto debajo.
{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> ))}
Cuándo usarlo
Úsala en páginas de empresa o producto donde tengas un vídeo explicativo o una demo que destacar. Funciona bien como sección de confianza a mitad de una landing page, después del hero y antes de los precios. Evítala si no tienes un vídeo que merezca mostrarse, o si la página ya tiene cerca una sección de medios pesada. En páginas muy orientadas a la conversión, la interacción del modal puede distraer; en ese caso, un vídeo inline en autoplay puede servir mejor.
Usado por
- Loom, Usa una miniatura de vídeo hero con un gran botón play que abre una demo de producto en un modal lightbox a pantalla completa.
- Notion, Presenta una sección de vídeo de producto centrada con una miniatura, un overlay de play y estadísticas clave bajo el reproductor de vídeo.
- Webflow, Coloca un vídeo de presentación de producto con overlay de play en sus páginas de marketing, que se abre en un modal.
FAQ
¿Por qué usar un iframe en lugar de un elemento video HTML nativo?
Un iframe permite incrustar vídeos alojados (YouTube, Vimeo, Wistia) sin auto-alojar el archivo. Pasa la URL de incrustación con los parámetros de autoplay en la query string. Para archivos auto-alojados, sustituye el iframe por un elemento video con `autoPlay` y `controls`.
¿Cómo detengo el vídeo cuando se cierra el modal?
Como el iframe se desmonta cuando `isPlaying` pasa a false, el navegador detiene la reproducción automáticamente. Si cambias a un elemento video nativo, llama a `videoRef.current.pause()` dentro del handler de cierre antes de cambiar el estado.
¿Puedo usar esta sección sin la fila de estadísticas?
Sí. El bloque de estadísticas se renderiza de forma condicional (`stats && stats.length > 0`), así que pasar un array vacío u omitir la prop lo elimina por completo sin efectos secundarios en el layout.
¿Es el modal accesible por teclado?
El botón de cerrar es un elemento button nativo con aria-label, por lo que recibe el foco y responde a Enter/Espacio. Para un comportamiento de trap-focus completo, añade una librería de focus trap o mueve manualmente el foco al botón de cerrar al abrir y restáuralo a la miniatura al cerrar.