Linha horizontal estilo Netflix em React
A fileira de pôsteres que você percorre em qualquer site de streaming não é um carrossel — é um menu. Nada se encaixa (snap), nada toca automaticamente; ela usa a rolagem nativa com inércia (momentum), com setas sobrepostas por cima. É exatamente isso que o react-horizontal-scrolling-menu entrega: seus cards, rolagem nativa e visibilidade por item para que as setas saibam quando se esconder.
Arraste, ou passe o mouse sobre a fileira — as setas aparecem gradualmente nas bordas, e cada uma desaparece quando aquele extremo da fileira é alcançado.
Edite este exemplo ao vivo no Storybook
Por que isso não é trabalho para um carrossel
Uma fileira estilo Netflix nunca mostra um slide por vez. Os itens são propositalmente cortados nas bordas — o pôster cortado é o indício visual que diz "tem mais". Motores de carrossel lutam contra isso: eles assumem a camada de gestos com transformações em JavaScript, encaixam (snap) nos limites dos slides e reimplementam a inércia que o navegador do usuário já tem. Em uma fileira de cards clicáveis, tudo isso é overhead.
A rolagem nativa te dá inércia, toque, trackpad e barra de rolagem de graça. As duas coisas que ela não te dá são as setas sobrepostas e saber quais cards estão na tela — e são exatamente essas duas coisas que esta biblioteca adiciona, via useIsVisible por item e um estado de seta sensível às bordas.
Os três detalhes que vendem o efeito
- As setas se sobrepõem ao conteúdo, elas não ficam ao lado dele. Renderize-as com posicionamento absoluto sobre as extremidades da fileira (a demo acima as passa através de
Headerpara que fiquem dentro do contexto do menu), mostre-as ao passar o mouse e esconda cada uma quandouseLeftArrowVisible/useRightArrowVisibleinformar que aquele extremo da fileira foi alcançado. - As bordas desaparecem em gradiente. Uma linha de CSS — um gradiente
mask-imageno contêiner de rolagem — substitui a lógica de "espiada" que os plugins de carrossel embutem para isso. - O arraste não pode disparar cliques. Um arraste de mouse que termina sobre um pôster não pode abri-lo. A receita de arraste para rolar rastreia o estado do arraste e engole exatamente esse clique.
Escalando: fileiras preguiçosas e trilhas longas
Interfaces de streaming empilham dezenas de fileiras com centenas de cards. Como os itens são DOM comum dentro de um contêiner de rolagem nativa, nada é renderizado novamente durante a rolagem — o exemplo de performance roda 300 itens sem virtualização. A visibilidade por item também te dá lazy-loading de imagens de graça: renderize um placeholder até que useIsVisible informe que o card está na tela.
Se a sua fileira precisa dar a volta no final, esse é o único lugar em que a semântica de slides realmente ajuda — veja a receita de loop infinito para a versão userland de ~60 linhas antes de recorrer a um motor de carrossel.
O padrão, no mínimo
Setas sobrepostas em uma fileira de rolagem nativa — a demo acima é essa estrutura mais estilização. O código-fonte completo, pronto para uso, com arraste e fade nas bordas, vem como o componente shadcn abaixo.
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 instale como um componente shadcn
O item de registro media-row é exatamente esse padrão — setas ao passar o mouse, fade em gradiente nas bordas, arraste para rolar — como um componente estilizado com Tailwind no seu components/ui/, seu para editar:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/media-row.json