npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-horizontal-scrolling-menu

v8.3.2

Published

Horizontal scrolling menu component for React with per-item visibility tracking. Supports mouse and touch devices, SSR-safe.

Readme

English · 简体中文 · 日本語 · 한국어 · Русский · Español · Português (BR) · Français · Tiếng Việt

React horizontal scrolling menu

npm NPM 下载量 npm 包体积(压缩 + gzip) CI 可接受雇佣

一个构建于浏览器原生滚动之上、并对每个项目进行可见性追踪的 React 横向滚动菜单组件。适用于分类栏、标签页条、筛选标签、画廊——任何你的应用 需要感知的一行内容。项目是你自己的组件,配以你自己的 CSS;菜单会响应其 父容器的宽度;导航可通过滚动条、触摸、鼠标滚轮、拖拽或你提供的箭头组件 进行。压缩 + gzip 后 5.7 kB。

示例

落地页 · 在线示例(Storybook,可在浏览器中编辑) · API · AI 代理技能

谁在使用

超过 20,000 个仓库依赖此库。以下五个可以直接查看——每个链接都指向使用它的组件中的 import 那一行, 并固定到某个提交,而不是指向 package.json

  • Our World in Data——他们文章渲染器中的核心洞见滑块;还有主题分面,其中包裹了一个 react-aria ToggleButton^8.2.0
  • Precious Plastic / ONE ARMY——共享组件包中的 VerticalList,直接依照本库文档构建。^8.2.0
  • erxes——POS 客户端中的分类菜单。^4.0.4
  • Reapit——应用构建器中的视口标签栏。^3.2.5
  • AWS Performance Dashboard——仪表板的 Tabs 组件;其 Arrows 直接使用 VisibilityContext。2024 年已归档,固定在 ^2.1.1

还登上了 React Status #257

快速开始

npm install react-horizontal-scrolling-menu

在用 shadcn/ui?一条命令即可将带样式的现成组件(边缘感知的箭头按钮、拖拽滚动、隐藏滚动条)直接安装到你的 components/ui/

npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/scroll-menu.json
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 style={{ width: '160px' }} data-visible={isVisible}>
      {title}
    </div>
  );
}

示例依赖的三点:

  • 每个项目都需要一个唯一的 itemId 属性——可见性追踪正是依赖它。React 的 key 作为后备方案。
  • styles.css 是一个单独的 import;JS 包绝不会注入 CSS。
  • 项目的宽度来自你自己的 CSS——菜单本身不做任何测量。

使用纯 JavaScript?去掉类型导入,照常使用 React.useContext(VisibilityContext) 即可。

与 AI 编程代理配合使用

基于旧版本训练的模型仍然会去使用 visibleElementsSeparator 项目以及 Arrows 属性——这些都已被移除——并凭空捏造一个从未存在过的 autoplay 属性。本包随附八个 SKILL.md 文件来阻止这种情况:按需通过 TanStack Intent 加载的、 按任务划分的指导,它与库一起发布版本,而不随任何网页更新。

npm install react-horizontal-scrolling-menu
npx @tanstack/intent@latest install   # 每个项目一次

install 会把技能发现机制加入你的代理的配置(CLAUDE.md.cursorrules 等);之后代理会按需从 node_modules/react-horizontal-scrolling-menu/skills/ 加载技能。也可以直接用 npx @tanstack/intent@latest listnpx @tanstack/intent@latest load react-horizontal-scrolling-menu#menu-setup 来列出或加载它们。

| 技能 | 何时加载 | | ---------------------- | ---------------------------------------------- | | menu-setup | 第一个可用的菜单、箭头、必需的 CSS 导入 | | menu-visibility | 屏幕上有什么,以及两端的箭头状态 | | menu-scrolling | scrollToItemapiRef、一次一页的分页 | | menu-interactions | 拖拽、滚轮与触摸——以及它们的事件处理工厂 | | menu-recipes | 自动播放、无限循环、加载更多:是配方,不是属性 | | menu-transitions-rtl | 动画时长、自定义缓动、从右到左 | | menu-testing-ssr | Next.js 与 RSC、Jest 模拟、Playwright | | menu-migration | 升级 v8 之前的代码,以及模型仍在凭空捏造的 API |

源码位于 skills/。无法加载 Intent 技能的代理应改为阅读 llms.txt——同样的事实, 浓缩进一个文件。

它做什么——又不做什么

构建于浏览器原生滚动之上:惯性、滚动条、触摸、滚轮与无障碍都来自浏览器, 而非一套重新实现的物理模拟。在此之上还有:通过 IntersectionObserver 实现的 逐项可见性、scrollToItem / scrollNext / scrollPrev、用于外部控制的 apiRef、Header 与 Footer 插槽、RTL、动态增删检测,以及贯穿始终的 TypeScript 类型。SSR 安全——落地页 会服务端渲染每一个示例。

没有轮播引擎:没有吸附或弹簧物理——如果你想要全屏图片滑块,请使用 Embla 或 Swiper。自动播放与无限循环也不是属性;它们是在公开 API 上各约六十行的配方, 可在 Storybook 中实时编辑 (无限循环自动播放)。 如果你需要一行知道什么可见的内容,就是它了。

示例

每个示例都可以在 Storybook 中实时编辑——每个 story 都附带一个加载了库的真实类型定义的 Monaco 编辑器。 涵盖:基本用法、一次滚动一个项目、鼠标拖拽、挂载时滚动到项目、点击居中、 动态添加项目、保存/恢复位置、项目动画、进度圆点、阻止页面滚动、自定义过渡、 无限循环、自动播放、垂直布局、底部的箭头、移动端滑动、RTL,以及 5000 项 压力测试。

辅助工具与 API

ScrollMenu 主组件的子组件(箭头、header、footer、项目)都可以使用 VisibilityContext 访问状态与回调。函数式回调也会收到上下文,例如 onWheelonScroll

属性与回调

| 属性 | 签名 | | ------------------------ | ------------------------------------------------------------------------------------ | | LeftArrow | 左箭头的 React 组件 | | RightArrow | 右箭头的 React 组件 | | Header | Header 的 React 组件 | | Footer | Footer 的 React 组件 | | onWheel | (VisibilityContext, event) => void | | onScroll | (VisibilityContext, event) => void,在滚动稳定之前触发 | | onInit | (VisibilityContext) => void | | onUpdate | (VisibilityContext) => void | | apiRef | React.RefObject | React.RefCallback | | options | IntersectionObserver 的选项——用于判定元素可见的 rootMarginthresholdratio | | containerRef | 滚动容器的 React.RefObject | React.RefCallback | | onMouseDown | (VisibilityContext) => (React.MouseEventHandler) => void | | onMouseLeave | (VisibilityContext) => (React.MouseEventHandler) => void | | onMouseUp | (VisibilityContext) => (React.MouseEventHandler) => void | | onMouseMove | (VisibilityContext) => (React.MouseEventHandler) => void | | onTouchMove | (VisibilityContext) => (React.TouchEventHandler) => void | | onTouchStart | (VisibilityContext) => (React.TouchEventHandler) => void | | onTouchEnd | (VisibilityContext) => (React.TouchEventHandler) => void | | itemClassName | Item 的 ClassName | | scrollContainerClassName | scrollContainer 的 ClassName | | wrapperClassName | 最外层 div 的 ClassName | | transitionDuration | 过渡时长(毫秒),默认 500,需要 noPolyfill={false} | | transitionBehavior | 'smooth' | 'auto' | 自定义函数,需要 noPolyfill={false} | | RTL | 启用从右到左的方向 | | noPolyfill | 默认 true(原生 scrollIntoView);设为 false 以启用过渡属性 |

请注意两种回调形态:onWheelonScroll 是简单的 (context, event) => void,而鼠标与触摸属性则是事件处理工厂——(context) => (event) => void。 请参阅 MouseDrag story 了解工厂模式的实际用法。

VisibilityContext

Hook(只能按照 Hook 的规则,在 ScrollMenu 之下渲染的组件内部调用它们):

| Hook | 签名 | | -------------------- | ------------------------------------------------------------------------ | | useIsVisible | (itemId: string | 'first' | 'last', defaultValue?: boolean) => boolean | | useLeftArrowVisible | () => boolean | | useRightArrowVisible | () => boolean |

值与函数:

| 属性 | 签名 | | --------------------- | ------------------------------------------------------ | | getItemById | itemId => IOItem | undefined | | getItemElementById | itemId => DOM Element | null | | getItemByIndex | index => IOItem | undefined | | getItemElementByIndex | index => DOM Element | null | | getNextElement | () => IOItem | undefined | | getPrevElement | () => IOItem | undefined | | isFirstItemVisible | boolean | | isItemVisible | itemId => boolean | | isLastItem | boolean | | isLastItemVisible | boolean | | menuVisible | { current: boolean } | | scrollNext | (behavior, inline, block, ScrollOptions) => void | | scrollPrev | (behavior, inline, block, ScrollOptions) => void | | scrollToItem | (item, behavior, inline, block, ScrollOptions) => void | | items | ItemsMap 类实例 | | scrollContainer | Ref |

items 类实例

ItemsMap 存储所有项目的相关信息,并提供获取当前可见项目以及前一个或下一个 项目的方法。你也可以订阅更新。

| 属性/方法 | 描述 | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------- | | subscribe | 订阅 itemIdfirstlastonInitonUpdate 的事件,例如 items.subscribe('item5', (item) => setVisible(item.visible)) | | unsubscribe | 在 useEffect 中用于清理,传入同一个回调实例 | | getVisible | 只返回可见项目 | | toItems | 返回所有项目的 id | | toArr | 返回所有项目 | | first | 返回第一个项目 | | last | 返回最后一个项目 | | prev | (itemId | Item) => 上一项 | undefined | | next | (itemId | Item) => 下一项 | undefined |

过渡与动画

transitionDurationtransitionBehavior'smooth''auto' 或自定义 函数)控制 scrollToItem 及滚动辅助函数的动画方式。两者都需要 noPolyfill={false}——默认的原生滚动会忽略它们。它们不能与 RTL 属性 组合使用。

请参阅 CustomTransition story 了解自定义缓动函数。

ScrollOptions

scrollToItemscrollPrevscrollNext 的最后一个参数会覆盖该次调用的 过渡属性:

scrollToItem(getItemElementById('item-5'), 'smooth', 'center', 'nearest', {
  duration: 800, // 毫秒
});

其他辅助工具

slidingWindow

获取上一组或下一组可见项目:

slidingWindow(allItems, visibleItems).prev();
// 或 .next()

getItemsPos

获取一组项目的第一个、中间和最后一个——例如滚动到上一页中间:

const prevGroup = slidingWindow(allItems, visibleItems).prev();
const { center } = getItemsPos(prevGroup);
scrollToItem(getItemById(center), 'smooth', 'center');

apiRef

向 ScrollMenu 传入一个 ref,完整的 VisibilityContext 值便会被赋给它——这对于 从菜单外部触发 scrollToItem 等函数很有用。ref 上的数据值可能过期,因此 建议调用函数:

apiRef.current.scrollToItem(apiRef.current.getItemElementById('item-3'));

你也可以直接通过 document.querySelector(`[data-key='${itemId}']`) 访问 某个项目的 DOM 元素。请参阅 ScrollToItem storyAddItemAndScrollToIt story

SSR

该库是 SSR 安全的:首次渲染会输出普通标记,IntersectionObserver 只在客户端 挂载。useIsVisibledefaultValue 参数控制服务端渲染的状态——典型的箭头 模式(('first', true) / ('last', false))会渲染一个禁用状态的左箭头和 启用状态的右箭头,与滚动到起点的行保持一致。

Next.js 注意事项

该包以 ESM 为先。在较旧的 Next.js 配置上,你可能会遇到 “Cannot use import statement outside a module”——将该包加入 transpilePackages 即可解决。

浏览器支持

需要 IntersectionObserverrequestAnimationFrame——所有现代浏览器均 支持。不支持 IE。

开发

git clone https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu
cd react-horizontal-scrolling-menu
pnpm run setup
pnpm run demo        # 示例应用(Next.js,端口 3003),库以 watch 模式运行
pnpm run demo-tanstack  # 示例应用(TanStack Start SSR,端口 3004)
pnpm run storybook   # 示例
pnpm test            # 单元 + e2e + storybook 测试

仓库里有两个集成示例应用——example-nextjsexample-tanstack(TanStack Start,在 workerd 中服务端渲染)——两者渲染同一个演示(鼠标拖拽、正文滚动 锁定、带控制面板的自定义动画),因此 e2e/ 中的同一套 e2e 测试会在两种框架 下对库进行测试,其中包含一条断言:菜单已经存在于服务端渲染的 HTML 中。

欢迎贡献与修正——fork、commit、发起 PR,也别忘了测试。请参阅 CONTRIBUTINGCHANGELOG

旧版 v1 API 的文档。

关于

Aleksandr Smyshliaev 自 2018 年起构建并维护——我的第一个 npm 包,并且 从 React 16.8 到 19 始终保持相同的公开 API。我是一名前端工程师(React / Next.js / TypeScript),目前接受外包与全职工作

给仓库一个 ⭐️,能帮助更多人发现这个库。