Dải cuộn ngang kiểu Netflix trong React
Dải áp phích bạn lướt qua trên mọi trang xem phim trực tuyến không phải là carousel — đó là một menu. Không có gì snap, không có gì tự phát; nó chạy trên cuộn quán tính gốc của trình duyệt với các mũi tên phủ lên trên. Đó chính xác là những gì react-horizontal-scrolling-menu cung cấp: thẻ của bạn, cuộn gốc, và khả năng hiển thị theo từng item để mũi tên biết khi nào cần ẩn.
Kéo nó, hoặc di chuột qua dải — các mũi tên mờ dần hiện lên ở các cạnh, và mỗi mũi tên biến mất khi đến đầu dải tương ứng.
Chỉnh sửa ví dụ này trực tiếp trong Storybook
Tại sao đây không phải là việc của carousel
Dải Netflix không bao giờ hiển thị từng slide một lúc. Các item bị cắt một phần ở các cạnh một cách có chủ ý — tấm áp phích bị cắt là tín hiệu thị giác nói rằng "còn nữa". Các engine carousel chống lại điều này: chúng chiếm lấy tầng cử chỉ bằng các phép biến đổi JavaScript, snap vào ranh giới slide, và tái triển khai quán tính mà trình duyệt của người dùng vốn đã có sẵn. Trên một dải thẻ có thể nhấp, tất cả những điều đó là chi phí thừa.
Cuộn gốc cho bạn quán tính, cảm ứng, trackpad và thanh cuộn miễn phí. Hai thứ nó không cho bạn là mũi tên phủ lên và biết thẻ nào đang trên màn hình — và đó chính là hai thứ thư viện này bổ sung, thông qua useIsVisible cho từng item và trạng thái mũi tên nhận biết cạnh.
Ba chi tiết làm nên hiệu ứng
- Mũi tên phủ lên nội dung, không nằm cạnh nó. Hiển thị chúng với vị trí tuyệt đối trên các đầu dải (demo ở trên truyền chúng qua
Headerđể chúng ở trong ngữ cảnh của menu), hiện chúng khi hover, và ẩn từng mũi tên khiuseLeftArrowVisible/useRightArrowVisiblebáo rằng đầu dải đó đã đạt tới. - Các cạnh mờ dần. Một dòng CSS — gradient
mask-imagetrên vùng chứa cuộn — thay thế logic "hé lộ" mà các plugin carousel cung cấp cho việc này. - Kéo không được kích hoạt click. Một thao tác kéo chuột kết thúc trên một áp phích không được mở nó. Công thức kéo để cuộn theo dõi trạng thái kéo và nuốt chính xác cú click đó.
Mở rộng quy mô: dải lười tải và rail dài
Giao diện xem phim trực tuyến xếp chồng hàng chục dải với hàng trăm thẻ. Vì các item là DOM thuần trong một vùng chứa cuộn gốc, không có gì render lại khi cuộn — ví dụ hiệu năng chạy 300 item mà không cần virtualization. Khả năng hiển thị theo từng item cũng cho bạn lazy-loading ảnh miễn phí: render một placeholder cho đến khi useIsVisible báo thẻ đã lên màn hình.
Nếu dải của bạn cần quay vòng ở cuối, đó là nơi duy nhất mà ngữ nghĩa slide thực sự hữu ích — xem công thức vòng lặp vô hạn cho phiên bản userland ~60 dòng trước khi tìm đến một engine carousel.
Mẫu hình, tối giản
Phủ mũi tên lên một dải cuộn gốc — demo ở trên chính là cấu trúc này cộng với styling. Mã nguồn drop-in hoàn chỉnh, có kéo và mờ cạnh, được cung cấp dưới dạng component shadcn bên dưới.
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); */Hoặc cài đặt nó như một component shadcn
Registry item media-row chính là mẫu hình này — mũi tên khi hover, mờ dần cạnh bằng gradient, kéo để cuộn — dưới dạng component được style bằng Tailwind trong components/ui/ của bạn, sẵn sàng để bạn chỉnh sửa:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/media-row.json