その横メニューは、何が見えているかを知っている
ブラウザ自身のスクロール上に構築された 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 上のレシピです。行を両端にクローンし、継ぎ目で 1 回 scrollLeft をジャンプさせ、scrollNext() を呼ぶタイマーを回します。ホバー、フォーカス、非表示タブで一時停止し、動きを減らす設定下では静止します。継ぎ目をまたいで、逆向きにさえドラッグできます。
メニューであり、カルーセルではない
Embla、Swiper、keen-slider は、画像スライダーを作るために JavaScript でスクロールを再実装しています——スナップポイント、スプリング物理、レンダーループ。このライブラリはそのどれも同梱しません。ブラウザネイティブのスクロールに乗り、ブラウザが与えない 1 つ——どの項目が画面上にあるかの把握を加えます。
全画面画像スライダーにとっては 間違った道具——そこでは Embla か Swiper を。カテゴリー行、タブストリップ、チップフィルター、そしてアプリが把握する必要のあるあらゆる行にとっては、正しい道具。
ネイティブスクロール
慣性、スクロールバー、タッチ、ホイール、アクセシビリティは、物理エンジンではなくブラウザから得られます。JavaScript がハイドレートする前からこの行はスクロールできます——このページのすべてのデモはサーバーレンダリングされます。
可視性追跡
IntersectionObserver がどの項目が画面上にあるかを報告します。useIsVisible(itemId) は 1 つのコンポーネントを 1 つの項目に購読させます——スクロール位置の計算は不要で、影響を受けた項目だけが再レンダリングされます。
必要なときは命令型
scrollToItem、scrollNext、scrollPrev、id やインデックスでの検索——メニュー内のコンテキストを通じて、あるいは外部から apiRef で。
あなたのコンポーネント、あなたの CSS
矢印、ヘッダー、フッター、そしてすべての項目はあなたが書くコンポーネントです。項目の幅はあなたの CSS。ライブラリが同梱するのは 210 バイトのレイアウトスタイルだけで、邪魔をしません。
クイックスタート
1 ファイル、設定不要:itemId を持つ項目、VisibilityContext を読む 2 つの矢印、そしてスタイルシートのインポート。
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、1 ページずつのページングmenu-interactionsドラッグ、ホイール、タッチ——とそれらのハンドラーファクトリーmenu-recipes自動再生、無限ループ、追加読み込み:プロパティではなくレシピmenu-transitions-rtlアニメーションのタイミング、カスタムイージング、右から左menu-testing-ssrNext.js と RSC、Jest モック、Playwrightmenu-migrationv8 以前のコードのアップグレードと、モデルが今もでっち上げる API
実際に出荷するレシピ
4 つの一般的なパターンを、重要な行とともにライブで。
アクティブなタブを中央に揃えるタブストリップ
タブをクリック: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>右から左、1 つのプロパティ
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 で 1 つの安定した API
先月 347,516 回、約 20,000 のリポジトリにダウンロードされました——2018 年から保守。
すべての例をブラウザーで編集できる
Storybook はプレイグラウンドを兼ねます。各ストーリーにはライブラリの実際の型定義を読み込んだ Monaco エディターが付属します。コードを変えて、再レンダリングを眺める——サンドボックスアカウントもローカルセットアップも不要です。
