Cómo crear un reproductor de podcast para blog con barras de waveform animadas en React
Un reproductor de podcast React para páginas de blog dibuja barras de waveform animadas con Framer Motion, un botón de reproducir/pausar que cambia las barras de alturas estáticas a animaciones de keyframes en bucle, y una lista de episodios desplazable, todo tematizado mediante CSS custom properties para adaptarse a cualquier paleta de colores sin tocar el componente.
- Stack: React 18 + Framer Motion 11 + Lucide React, ~140 líneas, cero dependencia de la API de Audio.
- 40 barras de waveform con alturas aleatorias generadas una sola vez mediante useMemo; cada barra se anima de forma independiente durante la reproducción.
- Accesible: el botón de reproducir/pausar lleva un aria-label que se actualiza según el estado.
- Totalmente tematizado: cada color se lee desde CSS custom properties (--color-accent, --color-border, etc.), compatible con los 7 presets.
- La waveform es solo interfaz visual, conecta un elemento <audio> real o un hook de la Web Audio API para añadir la reproducción real.
Blog Audio Player es una sección React que da vida a un feed de podcast con una waveform animada, una tarjeta de episodio destacado prominente y una lista compacta de episodios debajo. Las barras de waveform pasan de alturas congeladas a animaciones en bucle asíncronas en el instante en que el usuario pulsa reproducir, ofreciendo una respuesta visual inmediata sin una sola línea de keyframe CSS escrita a mano.
Anatomía
El componente tiene tres zonas visuales. Arriba, una cabecera centrada con un badge tipo pill (icono Headphones + etiqueta "Podcast"), un título de podcast y una descripción breve. Debajo, una tarjeta de episodio destacado con los metadatos de invitado/fecha, un título y una descripción, y luego una fila horizontal con el botón de reproducir/pausar, el subcomponente WaveformBars y la duración del episodio. Abajo, una lista vertical desplazable de filas de episodios más pequeñas, cada una con un icono Play circular, un título de episodio, una duración y una fecha.
Cómo funciona
El subcomponente WaveformBars recibe un único booleano `playing`. Al montarse, useMemo genera un array de 40 alturas aleatorias (entre el 20 y el 100% de 48px) que nunca cambian entre renders, por lo que las barras se ven consistentes. Cuando `playing` es false, cada barra se dibuja a su altura fija mediante una simple transición de 0,3s. Cuando `playing` pasa a true, la prop `animate` de cada barra recibe un array de tres keyframes de altura y una duración aleatoria entre 0,4s y 0,8s con `repeat: Infinity, repeatType: "reverse"`, creando el pulso de waveform escalonado y orgánico. Las barras del primer 40% del array usan `--color-accent` para insinuar el progreso "ya reproducido", el resto usa `--color-border`.
Cómo crearlo en React
Construir el subcomponente WaveformBars
Crea un componente que acepte `playing` y un `barCount` opcional (por defecto 40). Usa useMemo para generar el array de alturas aleatorias una sola vez. Recórrelo y renderiza un motion.div por barra, dando a cada una un `minWidth: 2` y `maxWidth: 6` fijos para que las barras se compriman bien en el espacio disponible.
const heights = useMemo( () => Array.from({ length: barCount }, () => 20 + Math.random() * 80), [barCount] );Alternar entre el estado estático y el animado
Pasa props `animate` distintas según el flag `playing`. Cuando es false, anima hacia la altura fija en píxeles. Cuando es true, pasa un array de tres keyframes para que Framer Motion lo repita en bucle automáticamente. Cada barra recibe una duración aleatoria para romper la sincronía visual.
animate={playing ? { height: [h * 0.3, h * 0.01 * 48, h * 0.5 * 0.01 * 48] } : { height: h * 0.01 * 48 } } transition={playing ? { duration: 0.4 + Math.random() * 0.4, repeat: Infinity, repeatType: "reverse" } : { duration: 0.3 } }Ensamblar la tarjeta de episodio destacado
Renderiza el nombre del invitado, la fecha, el título y la descripción del episodio encima de la fila del reproductor. La fila del reproductor es un contenedor flex: el botón circular de acento de reproducir/pausar a la izquierda, WaveformBars con `flex: 1` en el centro, y la cadena de duración a la derecha. Gestiona el estado de reproducción con un único booleano useState que alternas al hacer clic en el botón.
const [playing, setPlaying] = useState(false); // ... <button onClick={() => setPlaying(!playing)} aria-label={playing ? "Pause" : "Lecture"}> {playing ? <Pause /> : <Play />} </button> <WaveformBars playing={playing} /> <span>{feat.duration}</span>Añadir la lista de episodios con animaciones de entrada escalonadas
Recorre el array de episodios y renderiza cada uno como un motion.button (para la accesibilidad por teclado). Define `initial={{ opacity: 0, y: 10 }}` y `whileInView={{ opacity: 1, y: 0 }}` con un retardo de `0.12 + index * 0.05` segundos para que las filas aparezcan en cascada a medida que el usuario se desplaza hacia la sección.
Cuándo usarlo
Recúrrelo en blogs o sitios editoriales que publican contenido en audio: landing pages de podcast, newsletters centradas en entrevistas, o marcas de medios que quieren destacar episodios sin redirigir a los lectores a Spotify. Encaja bien entre un artículo destacado y un CTA de newsletter. Sáltatelo si tu sitio no tiene ningún contenido en audio, y recuerda que la waveform es decorativa, conecta una fuente de audio real antes de pasar a producción.
Usado por
- Spotify, Scrubbers estilo waveform y barras animadas indican el estado de reproducción en su reproductor web y sus páginas de podcast.
- Transistor, Reproductores de podcast integrables con previsualizaciones de waveform animadas usados por miles de podcasters independientes.
- Substack, Reproductores de audio dentro de los artículos con una interfaz de waveform minimalista permiten a los autores publicar ediciones habladas de sus newsletters.
- Overcast, Tarjetas compactas de lista de episodios con metadatos (duración, fecha, invitado) reproducen el patrón de maquetación de la lista de episodios del componente.
FAQ
¿Este componente reproduce audio de verdad?
No, la waveform y el estado de reproducir/pausar son puramente visuales. Para añadir reproducción real, conecta un elemento HTML <audio> (o la Web Audio API) y sincroniza sus llamadas `play()`/`pause()` con el mismo estado booleano que controla la animación de la waveform.
¿Por qué usar useMemo para las alturas de las barras?
Sin useMemo, las alturas aleatorias se regenerarían en cada render, haciendo que las barras salten a nuevas posiciones cada vez que el padre se vuelve a renderizar. Memoizarlas una sola vez garantiza una forma de waveform estable y coherente durante toda la vida del componente.
¿Cómo cambio el color de la waveform para que encaje con mi marca?
Las barras leen `--color-accent` (parte reproducida) y `--color-border` (parte no reproducida) desde CSS custom properties. Redefine esas dos variables en tu elemento wrapper o cambia a otro preset de tema y la waveform se actualiza automáticamente.
¿Puedo reducir el número de barras de waveform para una interfaz más ligera?
Sí. Pasa `barCount={20}` (o cualquier número) a WaveformBars. Menos barras dan un aspecto más escueto y minimal; más barras producen una waveform más densa. La maquetación flex del componente escala automáticamente el ancho de las barras para rellenar el espacio disponible.