이 가로 메뉴는,무엇이 보이는지 압니다
브라우저 자체 스크롤 위에 구축된 React 스크롤 메뉴 — 항목별 가시성 추적, 화살표, 드래그, 그리고 완전한 명령형 API. gzip 5.7 kB.
getVisible() → ['scifi', 'comedy', 'drama', 'horror', 'docs', 'kids']
This is the library, live — drag it. Dimmed tiles are the ones useIsVisible reports as off-screen.
캐러셀 엔진 없이 자동 재생
autoplay 프로퍼티는 없습니다. 이 레일은 공개 API 위의 레시피입니다. 행을 양 끝으로 복제하고, 이음새에서 scrollLeft를 한 번 점프시키고, scrollNext()를 호출하는 타이머를 돌립니다. 호버, 포커스, 숨은 탭에서 일시정지하고, 동작 줄이기 설정에서는 가만히 있으며 — 이음새를 넘어 거꾸로라도 드래그할 수 있습니다.
캐러셀이 아니라 메뉴
Embla, Swiper, keen-slider는 이미지 슬라이더를 만들기 위해 JavaScript로 스크롤을 다시 구현합니다 — 스냅 포인트, 스프링 물리, 렌더 루프. 이 라이브러리는 그중 어느 것도 제공하지 않습니다. 브라우저 네이티브 스크롤을 타고, 브라우저가 주지 않는 한 가지 — 어떤 항목이 화면에 있는지 정확히 아는 것을 더합니다.
전체 화면 이미지 슬라이더에는 잘못된 도구 — 거기서는 Embla나 Swiper를 쓰세요. 카테고리 행, 탭 스트립, 칩 필터, 그리고 앱이 파악해야 하는 모든 행에는 올바른 도구.
네이티브 스크롤
관성, 스크롤바, 터치, 휠, 접근성은 물리 엔진이 아니라 브라우저에서 나옵니다. JavaScript가 하이드레이트되기 전에 이 행은 스크롤됩니다 — 이 페이지의 모든 데모는 서버 렌더링됩니다.
가시성 추적
IntersectionObserver가 어떤 항목이 화면에 있는지 보고합니다. useIsVisible(itemId)는 컴포넌트 하나를 항목 하나에 구독시킵니다 — 스크롤 위치 계산도 없고, 영향받은 항목만 다시 렌더링됩니다.
필요할 때 명령형
scrollToItem, scrollNext, scrollPrev, id나 인덱스로 조회 — 메뉴 내부의 컨텍스트를 통해서, 또는 외부에서 apiRef로.
여러분의 컴포넌트, 여러분의 CSS
화살표, 헤더, 푸터, 그리고 모든 항목은 여러분이 작성하는 컴포넌트입니다. 항목 너비는 여러분의 CSS. 라이브러리는 210바이트의 레이아웃 스타일만 제공하고 비켜 서 있습니다.
빠른 시작
파일 하나, 설정 없음: itemId가 있는 항목, VisibilityContext를 읽는 두 개의 화살표, 그리고 스타일시트 임포트.
import React from 'react';
import {
ScrollMenu,
VisibilityContext,
type publicApiType,
} from 'react-horizontal-scrolling-menu';
import 'react-horizontal-scrolling-menu/dist/styles.css';
const items = Array.from({ length: 10 }, (_, i) => `item-${i + 1}`);
export function App() {
return (
<ScrollMenu LeftArrow={LeftArrow} RightArrow={RightArrow}>
{items.map((id) => (
<Card itemId={id} key={id} title={id} />
))}
</ScrollMenu>
);
}
function LeftArrow() {
const visibility = React.useContext<publicApiType>(VisibilityContext);
const isFirstVisible = visibility.useIsVisible('first', true);
return (
<button
disabled={isFirstVisible}
onClick={() => visibility.scrollPrev()}
>
←
</button>
);
}
function RightArrow() {
const visibility = React.useContext<publicApiType>(VisibilityContext);
const isLastVisible = visibility.useIsVisible('last', false);
return (
<button
disabled={isLastVisible}
onClick={() => visibility.scrollNext()}
>
→
</button>
);
}
function Card({ itemId, title }: { itemId: string; title: string }) {
const visibility = React.useContext<publicApiType>(VisibilityContext);
const isVisible = visibility.useIsVisible(itemId);
return (
<div className="card" data-visible={isVisible}>
<div>{title}</div>
<div>visible: {String(isVisible)}</div>
</div>
);
}The code on the left, running:
itemId는 모든 항목에 필수입니다 — 추적이 이렇게 동작합니다. React의 key는 폴백으로 동작합니다.
styles.css는 별도 임포트입니다. JS 번들이 CSS를 주입하는 일은 없습니다.
항목 너비는 여러분의 CSS에서 나옵니다 — 메뉴가 측정하는 것은 없습니다.
또는 코딩 에이전트에게 맡기세요
구버전으로 학습한 모델은 visibleElements, Separator 항목, Arrows 프로퍼티 — 모두 수년 전에 제거됨 — 에 여전히 손을 뻗고, 존재한 적 없는 autoplay 프로퍼티를 만들어 냅니다. 이 패키지는 이를 막기 위한 8개의 SKILL.md 파일을 제공합니다. 에이전트가 TanStack Intent를 통해 온디맨드로 로드하는 작업 범위 가이드로, 이 페이지가 아니라 라이브러리와 함께 버전이 관리됩니다.
패키지가 이미 설치된 프로젝트에서 한 번 실행하세요. 이후 에이전트는 node_modules/react-horizontal-scrolling-menu/skills/에서 스킬을 발견합니다.
menu-setup처음 동작하는 메뉴, 화살표, 필수 CSS 임포트menu-visibility화면에 무엇이 있는지, 그리고 양 끝의 화살표 상태menu-scrollingscrollToItem, apiRef, 한 번에 한 페이지씩 페이징menu-interactions드래그, 휠, 터치 — 그리고 해당 핸들러 팩토리menu-recipes자동 재생, 무한 루프, 더 불러오기: 프로퍼티가 아니라 레시피menu-transitions-rtl애니메이션 타이밍, 커스텀 이징, 오른쪽에서 왼쪽menu-testing-ssrNext.js와 RSC, Jest 목, Playwrightmenu-migrationv8 이전 코드 업그레이드, 그리고 모델이 여전히 만들어 내는 API
실제로 배포할 레시피
네 가지 일반 패턴을, 핵심 줄과 함께 라이브로.
활성 탭을 가운데 정렬하는 탭 스트립
탭을 클릭: scrollToItem에 inline: 'center'를 넘기면 행의 중앙으로 가져옵니다. 같은 호출로 start, end, 페이징도 처리합니다.
function Tab({ itemId, label }: { itemId: string; label: string }) {
const api = React.useContext<publicApiType>(VisibilityContext);
const centerOnClick = () => {
const el = api.getItemElementById(itemId);
if (el) api.scrollToItem(el, 'smooth', 'center');
};
return <button onClick={centerOnClick}>{label}</button>;
}칩 추가하고 스크롤
상태는 메뉴 밖에 두고 apiRef가 내부에 접근합니다. 필터를 추가하면 행이 그것을 따릅니다.
const apiRef = React.useRef<publicApiType>(null);
const lastAdded = React.useRef<string | null>(null);
function addChip(id: string) {
lastAdded.current = id;
setChips((current) => [...current, id]);
}
// After the new chip renders, scroll it into view from outside
// the menu — this is what apiRef is for.
React.useEffect(() => {
const id = lastAdded.current;
if (!id) return;
const el = apiRef.current?.getItemElementById(id);
if (el) apiRef.current?.scrollToItem(el, 'smooth', 'end');
lastAdded.current = null;
}, [chips]);
<ScrollMenu apiRef={apiRef}>…</ScrollMenu>끝이 보이면 더 불러오기
onUpdate가 마지막 항목이 보이게 되면 알려줍니다 — 바로 거기서 다음 페이지를 추가하세요. 스크롤 리스너도, 조정할 픽셀 임계값도 없습니다.
<ScrollMenu
onUpdate={(api) => {
// react in onUpdate, not onScroll — onScroll fires
// before the visibility state settles
if (api.items.last()?.visible) loadMore();
}}
>
{cards}
</ScrollMenu>오른쪽에서 왼쪽, 프로퍼티 하나
RTL이 스크롤 컨테이너의 방향을 뒤집고, 화살표와 페이징 로직이 따릅니다.
<ScrollMenu RTL LeftArrow={LeftArrow} RightArrow={RightArrow}>
{items.map((item) => (
<Item itemId={item.id} key={item.id} label={item.label} />
))}
</ScrollMenu>상자 안에 있는 것
- 항목별 가시성 훅 —
useIsVisible(itemId) - 화살표 상태를 위한
first/last헬퍼 scrollToItem·scrollNext·scrollPrev- 메뉴 외부에서 제어하는
apiRef - 드래그, 휠, 터치, 스크롤바 입력
- 동적 추가/제거 감지
- Header와 Footer 슬롯
slidingWindow+getItemsPos페이징 헬퍼- 오른쪽에서 왼쪽 지원
- 커스텀 전환 함수
- SSR 안전 — 이 페이지가 증명
- TypeScript 우선 —
publicApiType내보냄 - React 16.8 – 19에서 하나의 안정적인 API
지난달 약 20,000개 리포지토리에서 347,516회 다운로드 — 2018년부터 유지보수.
모든 예제를 브라우저에서 편집 가능
Storybook은 플레이그라운드를 겸합니다. 각 스토리에는 라이브러리의 실제 타입 정의를 로드한 Monaco 에디터가 함께 제공됩니다. 코드를 바꾸고 다시 렌더링되는 것을 보세요 — 샌드박스 계정도, 로컬 설정도 없습니다.
