Горизонтальный ряд в стиле Netflix на React
Ряд постеров, который вы пролистываете на любом стриминговом сайте, — это не карусель, а меню. Ничего не прилипает к позиции, ничего не воспроизводится автоматически: используется нативная инерционная прокрутка, поверх которой наложены стрелки. Именно это и предоставляет react-horizontal-scrolling-menu: ваши карточки, нативная прокрутка и отслеживание видимости каждого элемента, чтобы стрелки знали, когда скрываться.
Перетащите ряд или наведите на него курсор — стрелки проявляются по краям и исчезают по достижении соответствующего конца ряда.
Редактировать этот пример вживую в Storybook
Почему это не задача для карусели
Ряд в стиле Netflix никогда не показывает по одному слайду за раз. Элементы намеренно обрезаны по краям — обрезанный постер служит подсказкой «здесь есть ещё». Карусельные движки борются с этим: они забирают себе слой жестов через JavaScript-трансформации, привязываются к границам слайдов и заново реализуют инерцию, которая у браузеров пользователей уже есть. Для ряда кликабельных карточек всё это — лишние накладные расходы.
Нативная прокрутка бесплатно даёт инерцию, поддержку тач-экрана, трекпада и полосы прокрутки. Единственное, чего она не даёт, — это стрелки поверх контента и знание того, какие карточки видны на экране. Именно эти две вещи добавляет библиотека: через useIsVisible для каждого элемента и состояние стрелок, учитывающее края.
Три детали, которые продают эффект
- Стрелки накладываются на контент, а не располагаются рядом с ним. Разместите их с абсолютным позиционированием поверх концов ряда (демо выше передаёт их через
Header, чтобы они оставались внутри контекста меню), показывайте при наведении и скрывайте каждую, когдаuseLeftArrowVisible/useRightArrowVisibleсообщают, что соответствующий конец ряда достигнут. - Края затухают. Одна строка CSS — градиент
mask-imageна контейнере прокрутки — заменяет логику «подглядывания», которую для этого поставляют карусельные плагины. - Перетаскивание не должно вызывать клики. Перетаскивание мышью, завершившееся на постере, не должно его открывать. Рецепт drag-to-scroll отслеживает состояние перетаскивания и подавляет именно этот клик.
Масштабирование: ленивые ряды и длинные рейлы
Интерфейсы стриминговых сервисов складывают десятки рядов с сотнями карточек. Поскольку элементы — это обычный DOM в нативном контейнере прокрутки, при прокрутке ничего не перерисовывается — пример на производительность обрабатывает 300 элементов без виртуализации. Видимость по каждому элементу также бесплатно даёт ленивую загрузку изображений: рендерите заглушку, пока useIsVisible не сообщит, что карточка на экране.
Если ряд должен зацикливаться в конце — это как раз тот случай, где семантика слайдов действительно помогает: посмотрите рецепт бесконечного цикла с версией на ~60 строк пользовательского кода, прежде чем браться за карусельный движок.
Паттерн в минимальном виде
Наложите стрелки поверх ряда с нативной прокруткой — демо выше это и есть данная структура плюс стилизация. Готовый к использованию код с перетаскиванием и затуханием краёв доступен ниже в виде компонента 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); */Или установите как компонент shadcn
Элемент реестра media-row — это именно этот паттерн: стрелки при наведении, градиентное затухание краёв, перетаскивание для прокрутки — в виде компонента на Tailwind в вашей components/ui/, который можно редактировать:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/media-row.json