Rangée horizontale façon Netflix en React
La rangée d'affiches que vous parcourez sur chaque site de streaming n'est pas un carrousel — c'est un menu. Rien ne s'aligne, rien ne se lance automatiquement ; elle repose sur le défilement natif à inertie, avec des flèches superposées par-dessus. C'est exactement ce que fournit react-horizontal-scrolling-menu : vos cartes, le défilement natif, et une visibilité par élément pour que les flèches sachent quand se cacher.
Faites-la glisser, ou survolez la rangée — les flèches apparaissent en fondu sur les bords, et chacune disparaît quand son extrémité de la rangée est atteinte.
Modifier cet exemple en direct dans Storybook
Pourquoi ce n'est pas un travail de carrousel
Une rangée Netflix n'affiche jamais une seule diapositive à la fois. Les éléments sont volontairement coupés en partie sur les bords — l'affiche tronquée est l'indice visuel qui dit « il y en a plus ». Les moteurs de carrousel luttent contre cela : ils s'approprient la couche de gestes avec des transformations JavaScript, s'alignent sur des limites de diapositives, et réimplémentent l'inertie que les navigateurs de vos utilisateurs possèdent déjà. Sur une rangée de cartes cliquables, tout cela est superflu.
Le défilement natif vous donne l'inertie, le tactile, le trackpad et la barre de défilement gratuitement. Les deux choses qu'il ne vous donne pas sont les flèches superposées et le fait de savoir quelles cartes sont à l'écran — et ce sont justement les deux choses qu'ajoute cette bibliothèque, via useIsVisible par élément et un état des flèches conscient des bords.
Les trois détails qui vendent l'effet
- Les flèches se superposent au contenu, elles ne sont pas à côté. Positionnez-les en absolu par-dessus les extrémités de la rangée (la démo ci-dessus les fait passer par
Headerpour qu'elles restent dans le contexte du menu), affichez-les au survol, et masquez chacune quanduseLeftArrowVisible/useRightArrowVisiblesignalent que cette extrémité de la rangée est atteinte. - Les bords s'estompent en fondu. Une seule ligne de CSS — un dégradé
mask-imagesur le conteneur de défilement — remplace la logique d'« aperçu » que les plugins de carrousel embarquent pour cela. - Le glisser ne doit pas déclencher de clics. Un glisser de souris qui se termine sur une affiche ne doit pas l'ouvrir. La recette de glisser pour défiler suit l'état du glisser et absorbe exactement ce clic.
Faire monter en charge : rangées paresseuses et longs rails
Les interfaces de streaming empilent des dizaines de rangées avec des centaines de cartes. Comme les éléments sont du DOM ordinaire dans un conteneur de défilement natif, rien ne se re-rend au défilement — l'exemple de performance fait tourner 300 éléments sans virtualisation. La visibilité par élément offre aussi le chargement paresseux d'images gratuitement : affichez un espace réservé jusqu'à ce que useIsVisible signale que la carte est à l'écran.
Si votre rangée doit boucler à la fin, c'est le seul endroit où la sémantique des diapositives aide vraiment — voir la recette de boucle infinie pour la version côté application d'environ 60 lignes, avant de vous tourner vers un moteur de carrousel.
Le motif, minimal
Superposez des flèches sur une rangée à défilement natif — la démo ci-dessus, c'est cette structure plus le style. Le code source complet, prêt à l'emploi, avec glisser et fondu des bords, est fourni ci-dessous comme composant shadcn.
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); */Ou installez-le comme composant shadcn
L'élément de registre media-row est exactement ce motif — flèches au survol, fondu des bords en dégradé, glisser pour défiler — sous forme de composant stylé avec Tailwind dans votre components/ui/, à vous de l'éditer :
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/media-row.json