Fila horizontal estilo Netflix en React
La fila de pósters que recorres en cualquier plataforma de streaming no es un carrusel: es un menú. Nada encaja a la fuerza, nada se reproduce solo; usa el desplazamiento nativo con inercia, con flechas superpuestas encima. Eso es exactamente lo que ofrece react-horizontal-scrolling-menu: tus tarjetas, scroll nativo y visibilidad por elemento para que las flechas sepan cuándo ocultarse.
Arrástrala o pasa el cursor sobre la fila: las flechas aparecen sobre los bordes y cada una desaparece cuando se llega a ese extremo de la fila.
Edita este ejemplo en vivo en Storybook
Por qué esto no es tarea de un carrusel
Una fila estilo Netflix nunca muestra una sola diapositiva a la vez. Los elementos se cortan a propósito en los bordes: el póster cortado es la señal de que "hay más". Los motores de carrusel luchan contra esto: controlan la capa de gestos con transformaciones de JavaScript, encajan en los límites de cada diapositiva y reimplementan la inercia que el navegador de tus usuarios ya tiene. En una fila de tarjetas pulsables, todo eso es sobrecarga innecesaria.
El scroll nativo te da inercia, tacto, trackpad y barra de desplazamiento gratis. Las dos cosas que no te da son las flechas superpuestas y saber qué tarjetas están en pantalla, y esas son justo las dos cosas que añade esta librería, mediante useIsVisible por elemento y un estado de flechas consciente de los bordes.
Los tres detalles que venden el efecto
- Las flechas se superponen al contenido, no van al lado. Renderízalas con posición absoluta sobre los extremos de la fila (la demo de arriba las pasa a través de
Headerpara que permanezcan dentro del contexto del menú), muéstralas al pasar el cursor y oculta cada una cuandouseLeftArrowVisible/useRightArrowVisibleindiquen que se ha llegado a ese extremo de la fila. - Los bordes se desvanecen. Una línea de CSS —un gradiente
mask-imageen el contenedor de scroll— sustituye la lógica de "asomo" que los plugins de carrusel incluyen para esto. - El arrastre no debe disparar clics. Un arrastre de ratón que termina sobre un póster no debe abrirlo. La receta de arrastre para desplazar sigue el estado del arrastre y suprime justo ese clic.
Escalarlo: filas diferidas y rieles largos
Las interfaces de streaming apilan docenas de filas con cientos de tarjetas. Como los elementos son DOM normal dentro de un contenedor de scroll nativo, nada se vuelve a renderizar al desplazarse: el ejemplo de rendimiento mueve 300 elementos sin virtualización. La visibilidad por elemento también te da carga diferida de imágenes gratis: renderiza un marcador de posición hasta que useIsVisible indique que la tarjeta está en pantalla.
Si tu fila debe dar la vuelta al llegar al final, ese es el único caso en el que la semántica de diapositivas realmente ayuda: consulta la receta de bucle infinito para la versión de ~60 líneas hecha a mano, antes de recurrir a un motor de carrusel.
El patrón, en su forma mínima
Superpón flechas sobre una fila de scroll nativo: la demo de arriba es esta misma estructura más estilos. El código completo, listo para usar, con arrastre y desvanecido en los bordes, se distribuye como el componente shadcn de abajo.
function MediaRow({ children }: { children: React.ReactNode }) {
return (
<div className="group relative">
<ScrollMenu Header={<OverlayArrows />} wrapperClassName="edge-fade">
{children}
</ScrollMenu>
</div>
);
}
// Rendered as Header, so it lives inside the menu's VisibilityContext
// and can sit absolutely positioned over the row ends.
function OverlayArrows() {
const api = React.useContext<publicApiType>(VisibilityContext);
const atStart = api.useLeftArrowVisible();
const atEnd = api.useRightArrowVisible();
return (
<>
<button
className="overlay-arrow left-0"
hidden={atStart}
onClick={() => api.scrollPrev()}
>
←
</button>
<button
className="overlay-arrow right-0"
hidden={atEnd}
onClick={() => api.scrollNext()}
>
→
</button>
</>
);
}
/* .edge-fade — the Netflix edge, one CSS line:
mask-image: linear-gradient(to right,
transparent, black 40px, black calc(100% - 40px), transparent); */O instálalo como componente shadcn
El elemento del registro media-row es exactamente este patrón —flechas al pasar el cursor, desvanecido con gradiente en los bordes, arrastre para desplazar— como componente con estilos Tailwind en tu components/ui/, listo para que lo edites:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/media-row.json