React의 Netflix 스타일 가로 스크롤 행
모든 스트리밍 사이트에서 넘겨보는 포스터 행은 캐러셀이 아니라 메뉴입니다. 아무것도 스냅되지 않고 자동 재생도 없습니다. 위에 화살표를 겹쳐 놓은 채 네이티브 모멘텀 스크롤을 그대로 사용합니다. react-horizontal-scrolling-menu가 제공하는 것이 정확히 이것입니다: 여러분의 카드, 네이티브 스크롤, 그리고 화살표가 언제 숨어야 하는지 아는 항목별 가시성.
드래그하거나 행에 마우스를 올려보세요 — 화살표가 가장자리 위로 서서히 나타나고, 행의 끝에 도달하면 각각 사라집니다.
캐러셀로 풀 문제가 아닌 이유
Netflix 행은 한 번에 하나의 슬라이드만 보여주지 않습니다. 항목은 일부러 가장자리에서 잘려 보이는데, 이 잘린 포스터가 "더 있다"는 것을 알려주는 어포던스입니다. 캐러셀 엔진은 이와 반대로 동작합니다: JavaScript 트랜스폼으로 제스처 레이어를 직접 소유하고, 슬라이드 경계에 스냅하며, 사용자 브라우저가 이미 가지고 있는 모멘텀을 다시 구현합니다. 클릭 가능한 카드 행에서는 이 모든 것이 오버헤드일 뿐입니다.
네이티브 스크롤은 모멘텀, 터치, 트랙패드, 스크롤바를 공짜로 제공합니다. 다만 오버레이 화살표와 어떤 카드가 화면에 보이는지 아는 것, 이 두 가지는 제공하지 않습니다 — 이 라이브러리가 추가하는 것이 바로 이 두 가지로, 항목별 useIsVisible과 가장자리 인식 화살표 상태로 구현됩니다.
효과를 완성하는 세 가지 디테일
- 화살표는 콘텐츠 옆이 아니라 위에 겹쳐집니다. 행 끝부분에 절대 위치로 렌더링하고(위 데모는 메뉴의 컨텍스트 안에 머물도록
Header를 통해 전달합니다), 호버 시 표시하며,useLeftArrowVisible/useRightArrowVisible가 해당 끝에 도달했다고 알릴 때 각각 숨깁니다. - 가장자리는 페이드됩니다. 스크롤 컨테이너에 적용한
mask-image그라디언트 한 줄이, 캐러셀 플러그인들이 이를 위해 제공하는 "peek" 로직을 대신합니다. - 드래그는 클릭을 발생시켜서는 안 됩니다. 포스터 위에서 끝나는 마우스 드래그가 해당 포스터를 열어서는 안 됩니다. 드래그 스크롤 레시피가 드래그 상태를 추적해 바로 그 클릭을 흡수합니다.
확장하기: 지연 로딩 행과 긴 레일
스트리밍 UI는 수백 개의 카드가 담긴 행을 수십 개씩 쌓습니다. 항목이 네이티브 스크롤 컨테이너 안의 평범한 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 레지스트리 아이템은 이 패턴 그대로입니다 — 호버 화살표, 그라디언트 가장자리 페이드, 드래그 스크롤 — components/ui/에 Tailwind 스타일 컴포넌트로 설치되어 자유롭게 수정할 수 있습니다:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/media-row.json