意义
下拉菜单在很多网站都能见到,鼠标悬浮在导航元素上时会自动弹出子菜单。
好处:
- 用于导航分类
- 可以在有限的视区内展示更多的内容
DOM 结构
导航的语义化标签为 nav,内容为列表 ul,列表元素水平分布可以利用浮动或者 flex。
例如:
<nav>
<ul>
<li>导航1</li>
<li>导航2</li>
<li>导航3</li>
</ul>
</nav>
下拉菜单也是列表结构,例如:
<nav>
<ul>
<li>
<a>导航1</a>
<ul>
<li>导航1-1</li>
<li>导航1-2</li>
<li>导航1-3</li>
</ul>
</li>
<li>导航2</li>
<li>导航3</li>
</ul>
</nav>
如果是非自动(hover)弹出可以直接用 HTML5/select & options。
响应鼠标事件
在鼠标离开 nav-item 及其所有子元素时,关闭 dropdown menu,对应的 DOM Event 是 mouseleave。(不是 mouseout)
在鼠标进入 nav-item 根元素时,打开 dropdown menu,对应的 DOM Event 是 mouseenter。(比 mouseover 准确)
Dropdown 的 TSX 结构
列表由数组渲染生成,把 items 看作 Functor,将渲染函数应用到每个元素。即 fmap renderToTSX items。
type Anchor = {
name: string
href?: string
}
const Dropdown = ({ items }: { items: Anchor[] }) => (
<ul className="head-dropdown">
{items.map(({ name, href }) => (
<li>
<a href={href}>{name}</a>
</li>
))}
</ul>
)
对应的层叠样式表
为了避免因 dropdown menu 动态显示导致 DOM 结构 reflow,所以利用绝对定位让它脱离文档流。
.head-dropdown {
position: absolute;
z-index: 999;
}
使用 Hook/useState
因为要动态显示 dropdown menu,所以属于异步的 DOM 操作。使用 setState 来实现。
type NavItemProps = Anchor & {
items: Anchor[]
}
const NavItem = ({ name, href, items }: NavItemProps) => {
const [display, setComponent] = useState(<></>)
const visible = () => setComponent(<Dropdown items={items} />)
const hidden = () => setComponent(<></>)
return (
<li className="nav-item" onMouseEnter={visible} onMouseLeave={hidden}>
<a href={href}>{name}</a>
{display}
</li>
)
}
nav-item 的层叠样式表。需要兼容 IE10 以下就使用浮动。
.nav-item {
float: left;
padding: 0 0.5rem;
}
App
const App = () => (
<nav>
<ul>
<NavItem
name="item1"
items={[
{ name: "subitem1", href: "#" },
{ name: "subitem2", href: "#" },
{ name: "subitem3", href: "#" }
]}
/>
</ul>
</nav>
)