中文 | English
navbar-153 是一个 React 导航栏组件,包含一组触发器和一组对应的菜单面板,用户可以通过触发器展开、切换、收起菜单面板。导航栏通常出现在网站的顶部,提供最希望用户访问的链接和其它控件。navbar-153 有下面这些特性:
- 🍯 流畅的过渡动画;
- 🎹 键盘导航;
- ♿️ 屏幕阅读器导航;
- 🎨 高度自定义。
navbar-153 is a React navigation menu component that includes a set of triggers and a corresponding set of menu panels. For more information, please refer to the English README or demo.
您可以打开演示链接,查看使用效果。
在 Chrome 中,可以打开“短暂地突出显示焦点对象”无障碍功能,来可视化查看组件的焦点走向。
在地址栏输入 chrome://settings/accessibility
,或者在“设置 -> 无障碍”中,可以设置“短暂地突出显示焦点对象”。
使用 npm 安装 navbar-153:
npm install navbar-153
下面是安装之后,使用组件的大致形态,完整的范例可以打开仓库的 dark-space
文件夹(Nextjs 项目)查看:
import N from "navbar-153";
const { Trigger, Item, Content } = N;
function MyNavBar() {
const contentItemStyle = props => ({ ...props.style, width: "100%", flexShrink: 0 });
return (
<N style={{ position: "relative" }}>
<Trigger style={{ display: "flex", gap: 8 }}>
<Item><a href="https://github.com/wswmsword/navbar-153">Repo</a></Item>
<Item>{props => <button {...props}>Trigger 1</button>}</Item>
<Item>{props => <button {...props}>Trigger 2</button>}</Item>
<Item>{props => <button {...props}>Trigger 3</button>}</Item>
</Trigger>
<Content className="panelsWrapper">
<Item>{props => <div {...props} style={contentItemStyle(props)}>Content 1</div>}</Item>
<Item>
{(props, head, tail) => <div {...props} style={contentItemStyle(props)}>
<a href="https://react.dev/?uwu" ref={head}>React</a>
vs
<a href="https://vuejs.org/?uwu" ref={tail}>Vue</a>
</div>}
</Item>
<Item>{props => <div {...props} style={contentItemStyle(props)}>Content 3</div>}</Item>
</Content>
</N>
);
}
export default MyNavBar;
导航栏组件由下面几个组件组合而成,分别是 <N>
、<Trigger>
、<Content>
和 <Item>
。
导航栏组件的最外层组件,使用的时候默认导入,例如:
import N from "navbar-153";
<N>
会被渲染成 <nav>
作为导航栏组件的最外层,<N>
接收任何用于 HTML 元素的 props,以及下面这些额外选项:
-
motion
,boolean,是否减弱动态效果; -
dur
,number,定义过渡动画的持续时间(s); -
gap
,number,设置面板和触发器之间的距离(px); -
dynamicWidth
,boolean,允许面板的宽度变化; -
onlyKeyFocus
,boolean,设置焦点仅在键盘控制时触发转移; -
close
,boolean,切换面板时跟随触发器的位置。
像下面这样导入 <Trigger>
组件:
import N from "navbar-153";
const { Trigger, Content, Item } = N;
<Trigger>
会被渲染成 <div>
,作为 <nav>
的子元素,<Trigger>
接收任何内置的 props。<Trigger>
组件内部是一组触发器,因此可以在 <Trigger>
上传入 className
或 style
来定义触发器的布局。
<Content>
的导入方式和 <Trigger>
相同。<Content>
组件内部是一组内容面板,每一个内容面板都按顺序对应了 <Trigger>
组件内部的触发器,<Content>
与 <Trigger>
应该在同一层级,<Content>
组件会被渲染成两层 <div>
,接收任何内置的 props,这些 props 最终生效在外层 <div>
上。可以向 <Content>
传入 className
或 style
来为面板设置阴影等样式。
-
inner
,inner
中的对象会作为 props 传入<Content>
渲染的内层<div>
上; -
customTransProps
,自定义面板切换时的过渡动画,传入一个对象,对象的键是 CSS 属性,值是长度为 2 或 3 的数组,长度 2 的数组表示动画拥有起止两个状态,长度 3 的数组表示动画有进入前、正常、退出后三个状态吗,除了这两种数组类型,还接受一个特殊的transition
属性,这个属性的值是一个字符串,用于设置切换动画时的过渡时间,如果不设置将应用默认值(transition: all ${dur}s
)。
例如设置渐变进入、离开的自定义过渡动画:
{
"opacity": [0, 1],
"transform": ["translate(0)", "translateX(-280px)", "translateX(280px)"],
"transition": "opacity .4s, transform .4s"
}
<Item>
的导入方式和 <Trigger>
相同。<Item>
必须作为 <Trigger>
或 <Content>
的直接子元素,<Item>
在 <Trigger>
中是触发器,<Item>
在 <Content>
中是内容面板,<Item>
不接收任何参数。
<Item>
在 <Trigger>
中时,<Item>
的子元素可能是一个触发器,也可能是一个普通的元素,每个触发器都对应了一个内容面板。当子元素是一个组件,或是一个具体的元素,例如 <a>
,则 <Item>
会被视为一个普通元素。当子元素 children 是一个 render props,则会被视为一个触发器,例如下面这样:
<Item>{props => <button {...props}>Trigger 1</button>}</Item>
上面例子中的 props 必须要传递给触发器元素,这些 props 包含了事件、ARIA 标签等必须的信息。
<Item>
在 <Content>
中时,<Item>
的子元素是一个内容面板,子元素 children 必须是一个 render props,例如下面这样:
<Item>
{(props, head, tail) => <div {...props} style={{ ...props.style, width: "100%", flexShrink: 0 }}>
<a href="https://react.dev/?uwu" ref={head}>React</a>
vs
<a href="https://vuejs.org/?uwu" ref={tail}>Vue</a>
</div>}
</Item>
上面例子中的 props 必须要传递给内容面板元素,这些 props 同样包含了事件、ARIA 标签等必须的信息,render props 的入参还提供了第二个参数 head
和第三个参数 tail
,如果内容面板中包含可聚焦的元素,必须要分别把 head
作为 ref
传递给第一个可聚焦元素,把 tail
作为 ref
传递给最后一个可聚焦元素,这两个 ref
会完成键盘 Tab 导航的工作,如果内容面板中只展示,没有聚焦元素,可以忽略这两个参数。
Key | Description |
---|---|
Tab | 当焦点在触发器上,将从前往后逐个聚焦,当焦点在内容面板中,焦点将在头元素和尾元素之间循环 |
Space Enter | 当焦点在触发器上,按下按键,会展开或收起内容面板 |
Esc | 当焦点在内容面板中,按下按键会收起面板,焦点回到触发器 |
macOS 里,用户在 Firefox 可能无法使用 Tab 聚焦链接元素,需要用户执行下面的步骤:打开“系统设置”,打开“键盘”,打开“键盘导航”。
在项目根目录执行下面的命令,监听组件源码的变化,并实时更新输出:
npm run watch
保持上面的监听命令打开,再打开新的终端会话,执行下面的命令运行引入了源码组件的 React Demo 应用,在更改源码时,在浏览器实时查看效果:
cd examples/demo
npm i
npm run dev
下面我会带您看到这个项目的目标,大概是终端用户、开发者和源码维护三个方面,最终希望您也能够参与进来完善这个项目:
- 可访问性
- 有正确的 ARIA 标签,能够通过安卓 TalkBack 和 iOS、MacOS 的 VoiceOver 的验证
- 能够完全通过键盘控制
- 能够切换打开与关闭过渡动画
- 流畅的过渡动画
- 不错的性能
- 良好的开发体验
- 有符合直觉的使用形态
- 保留导航栏功能核心,不侵犯开发者的自定义空间
- 简易的文档
- 编码整理
- 无需遵循特定的格式规范,请自由使用习惯的格式
- 编码中的命名合适,在没有找到合适的命名前有详细的注释辅助理解
- 上浮和下沉函数,找到合适的抽象层
查看一些原理。
查看更新日志。
查看语义化版本 2.0.0。
查看 MIT License。
请随意 Issue、PR 和 Star,您也可以支付该项目,支付金额由您从该项目中获得的收益自行决定。