你一定遇到过那种特别难用的移动端菜单。
点一下汉堡图标,等抽屉慢吞吞滑出来。然后,你在十几个链接里找自己真正需要的那个。点错,返回,再点。三十秒后,你已经关掉页面,去看竞争对手的网站了。
最可怕的是,你甚至说不清第一个网站到底哪里烦。它可能不是传统意义上的“设计很差”——图标也许没问题,颜色也许符合品牌,动效也许还挺顺。
真正出错的地方,藏得更深:有人为这个任务选错了菜单模式。
移动端导航,其实有 12 种常见模式。而它们最终都在回答同一组问题:用户多久需要它?它在产品结构里有多深?
就这两个问题。如果一个用户一场会话要点十次的入口,被你塞进汉堡抽屉里,那每次多出来的点击和动画,都会变成摩擦。如果你把 40 个设置分类塞进底部 Tab Bar,它就会变成没人想要的横向滚动灾难。
这两种失败,都不是审美问题,而是模式和任务不匹配。
下面这 12 种移动端菜单模式,每一种都有自己的频率和深度位置。看完之后,你选菜单时就不会再问“哪个看起来更高级”,而是会先问:这个入口,用户到底多常用?它到底应该露在多浅的位置?
1. 汉堡抽屉菜单
三个横线。点一下,一个面板从左侧滑出,通常覆盖屏幕宽度的 70% 到 80%,后面的内容会被半透明遮罩压暗。
<div class="p1-drawer" id="p1Drawer">
<div class="p1-avatar"></div>
<div class="p1-name">Naila Rehman</div>
<div class="p1-email mono">@naila · Free plan</div>
<ul class="p1-nav">
<li><a href="#" class="active"><span class="dot"></span>Today</a></li>
<li><a href="#"><span class="dot"></span>Archive</a></li>
<li><a href="#"><span class="dot"></span>Favorites</a></li>
<li><a href="#"><span class="dot"></span>Shared with me</a></li>
<li><a href="#"><span class="dot"></span>Trash</a></li>
</ul>
</div>
.p1-drawer {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 78%;
background: var(--ink-soft);
transform: translateX(-105%);
transition: transform 0.38s cubic-bezier(0.65, 0, 0.35, 1);
z-index: 30;
}
.p1-drawer.open {
transform: translateX(0);
}
这里的 -105% 比 -100% 更重要。它会把抽屉边缘彻底推出视口,避免因为舍入误差,在静止状态下露出一条细细的边。

频率和深度:低频,深层。这个模式适合那些用户一场会话只碰一两次的东西,比如切换账号、退出登录、不常用设置、很少在任务中途打开的入口。
但只要抽屉里的某个项目开始被频繁点击,问题就来了——额外的一次点击,额外的一段动画。原本用户说不出口的“有点烦”,就是从这里开始积累的。
2. 侧边栏导航
一条常驻的竖向图标栏,通常宽 56 到 64px,固定在屏幕左侧。
<nav class="p2-rail" id="p2Rail">
<button class="p2-item active" data-p2>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 11l9-7 9 7v9a1 1 0 01-1 1h-5v-6H9v6H4a1 1 0 01-1-1z" />
</svg>
<span class="p2-label mono">Home</span>
</button>
</nav>
.p2-rail {
width: 64px;
flex-shrink: 0;
background: var(--ink-soft);
border-right: 1px solid var(--line);
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
gap: 6px;
}
.p2-item.active {
background: var(--accent);
color: var(--ink);
}

频率和深度:高频,浅层。它适合 3 到 5 个用户经常访问的目的地,并且这些入口需要一直露出来。它最大的好处是,不吃掉垂直空间。
但问题也很明显:它吃横向空间。在平板或折叠屏上,64px 是一个小成本。但在 375px 宽的手机竖屏上,它几乎吃掉了五分之一的可用宽度。所以这个模式真正适合的是平板、折叠屏、大屏设备,不是标准手机竖屏。
3. 底部 Tab Bar
3 到 5 个目的地,固定在屏幕底部。永远可见,拇指随时能点到,不需要额外打开菜单。
<nav class="p3-tabbar" id="p3Bar">
<button class="p3-tab active" data-p3>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 11l9-7 9 7v9a1 1 0 01-1 1h-5v-6H9v6H4a1 1 0 01-1-1z" />
</svg>
<span class="lab">HOME</span>
</button>
<button class="p3-tab" data-p3>...</button>
</nav>
.p3-tabbar {
height: 64px;
display: flex;
border-top: 1px solid var(--line);
background: var(--ink-soft);
}
.p3-tab.active::before {
content: "";
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 26px;
height: 2.5px;
background: var(--accent);
}

64px 不是随便定的。 Apple 对舒适点击区域的建议大约是 44pt。64px 可以同时容纳图标、文字标签和足够的上下留白,避免用户拇指误点。再小一点,误触率就会上升。尤其是在大屏手机上,底部栏距离单手握持的自然区域更远,容错空间更重要。
频率和深度:最高频,最浅层。这就是为什么底部 Tab Bar 会成为很多移动应用的默认选项。如果你不知道新 App 应该先从哪种导航开始,通常可以先从它开始。只有当你有明确理由时,再换成其他模式。
4. FAB 快捷操作菜单
一个圆形悬浮按钮,通常放在右下角。点击之后,展开 2 到 4 个小操作按钮。
<div class="p4-fab-wrap" id="p4Wrap">
<button class="p4-fab" id="p4Main">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4">
<path d="M12 5v14M5 12h14" />
</svg>
</button>
<button class="p4-mini">...</button>
<button class="p4-mini">...</button>
<button class="p4-mini">...</button>
</div>
.p4-mini {
opacity: 0;
transform: translateY(10px) scale(0.7);
transition: all 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
pointer-events: none;
}
.p4-fab-wrap.open .p4-mini {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.p4-fab-wrap.open .p4-mini:nth-child(2) { transition-delay: 0.03s; }
.p4-fab-wrap.open .p4-mini:nth-child(3) { transition-delay: 0.06s; }
.p4-fab-wrap.open .p4-mini:nth-child(4) { transition-delay: 0.09s; }

每个小按钮比前一个晚 30ms 出现。这就是自然级联展开和同时弹出之间的差别。前者像动作,后者像开关。
频率和深度:一个超高频主操作,下面挂 2 到 4 个紧密相关的次操作。比如“创建”下面放创建文档、创建任务、创建笔记。如果这些子操作之间关系不够近,FAB speed dial 就会变得吵,它会从“快捷入口”变成一个小型杂货架。
5. 底部 Sheet
一个从屏幕底部升起的面板,覆盖一部分屏幕。可以点遮罩关闭,也可以向下拖拽关闭。
<div class="p5-sheet" id="p5Sheet">
<div class="p5-handle"></div>
<h4>Share to</h4>
<div class="p5-row">
<span class="ic">...</span>Send as message
</div>
<div class="p5-row">
<span class="ic">...</span>Email a copy
</div>
</div>
.p5-sheet {
position: absolute;
left: 0;
right: 0;
bottom: 0;
border-radius: 22px 22px 0 0;
transform: translateY(100%);
transition: transform 0.38s cubic-bezier(0.32, 0.72, 0, 1);
z-index: 30;
}
.p5-sheet.open {
transform: translateY(0);
}
.p5-handle {
width: 36px;
height: 4px;
border-radius: 4px;
background: var(--line);
margin: 0 auto 18px;
}

顶部那条 handle 不是装饰。过去十年,主流移动系统已经教育用户:一条小横条,意味着“我可以拖”。你当然可以省掉它,用户最后也能猜出来,但会更慢,也会有更多人因为不确定而直接点遮罩退出。
频率和深度:中频,浅层,并且强依赖当前上下文。底部 Sheet 比整页跳转好的地方在于:用户还能看见一部分原来的界面,他没有完全丢失上下文。但这个优势只在内容很短、选择很明确时成立。如果 Sheet 里的内容长到需要多屏滚动,那它就不再是上下文选择,而是变成了一个被硬塞进组件里的页面。
6. Overflow 菜单
右上角三个竖点。点击之后,一个小下拉菜单从角落展开。
<button class="icon-btn" id="p6Toggle">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="5" r="1.6" />
<circle cx="12" cy="12" r="1.6" />
<circle cx="12" cy="19" r="1.6" />
</svg>
</button>
.p6-menu {
position: absolute;
top: 48px;
right: 16px;
width: 170px;
transform-origin: top right;
transform: scale(0.85);
opacity: 0;
pointer-events: none;
transition: all 0.18s ease;
}
.p6-menu.open {
transform: scale(1);
opacity: 1;
pointer-events: auto;
}

这里真正起作用的是:
transform-origin: top right;
菜单会从三个点所在的同一个角落向外展开。用户会觉得它是从触发点长出来的,而不是屏幕上某个地方突然冒出来。
频率和深度:低频,浅层。但它通常只服务于某一个具体对象,而不是整站导航,比如某条消息、某个文件、某张卡片的更多操作。如果里面有危险操作,比如删除,应该放最后,用分割线隔开,并使用不同颜色。这样用户快速扫一眼时,不用读完文字,也能知道“这个选项不一样”。
7. 网格菜单
类似启动器的图标网格。所有入口尺寸一样、权重一样、视觉处理一样,没有主次。
<div class="p7-grid">
<button class="p7-cell">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<rect x="3" y="3" width="7" height="7" />
<rect x="14" y="3" width="7" height="7" />
<rect x="3" y="14" width="7" height="7" />
<rect x="14" y="14" width="7" height="7" />
</svg>
APPS
</button>
</div>
.p7-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
padding: 20px;
}
.p7-cell {
aspect-ratio: 1;
border-radius: 16px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

频率和深度:这是 12 种模式里,唯一一个主动拒绝回答“频率”问题的模式。因为每个格子都一样大、一样重、一样被对待。这只有在 6 到 9 个目的地真的没有自然顺序时才合理。一旦其中某个入口被明显更频繁使用,扁平网格就开始隐藏真实存在的层级。这时候,侧边栏或 Tab Bar 会比网格更诚实。不要假装每个入口都一样重要——如果它们不一样,界面应该承认这一点。
8. 分段控制器
它严格来说不是导航。它切换的是当前屏幕上的内容视图,而不是把用户带到新的地方。
<div class="p8-seg" id="p8Seg">
<div class="p8-thumb" id="p8Thumb"></div>
<button class="active mono" data-p8>DAY</button>
<button class="mono" data-p8>WEEK</button>
<button class="mono" data-p8>MONTH</button>
</div>
.p8-thumb {
position: absolute;
top: 4px;
bottom: 4px;
left: 4px;
width: calc(33.333% - 4px);
background: var(--accent);
border-radius: 9px;
transition: transform 0.3s cubic-bezier(0.65, 0, 0.35, 1);
z-index: 1;
}
document.querySelectorAll("#p8Seg [data-p8]").forEach((btn, i) => {
btn.addEventListener("click", () => {
document.querySelectorAll("#p8Seg [data-p8]").forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
p8Thumb.style.transform = `translateX(${i * 100}%)`;
});
});

这里只需要一个 thumb 元素在当前激活标签下面滑动。通过 index 计算 translateX,就能替代三个单独的选中状态。
频率和深度:不适用。这正是重点。分段控制器经常被误当成导航——如果点击某一项后,用户应该进入一个新位置,并且返回按钮应该记录这次跳转,那它就不是分段控制器,而是穿着 toggle 外衣的导航。而这会让返回按钮、页面历史和用户预期一起出问题。
9. 手风琴列表
一组纵向堆叠的标题。点击标题,就在原地展开内容。通常打开一个时,会收起前一个。
<div class="p9-item open" data-p9>
<button class="p9-head">
<span>Workspace</span><span class="p9-chev">⌄</span>
</button>
<div class="p9-body">
<div class="p9-body-inner">Members · Billing · Integrations</div>
</div>
</div>
.p9-body {
max-height: 0;
overflow: hidden;
transition: max-height 0.32s ease;
}
.p9-item.open .p9-body {
max-height: 120px;
}

这个固定的 max-height 是一个捷径,不是理想解。CSS 不能直接从 height: 0 过渡到 height: auto,所以很多人会写一个硬编码的高度上限。如果内容短而且可预测,这样可以接受;但如果内容长度不可控,就应该用 JavaScript 读取 scrollHeight,再动画到实际高度。否则,内容可能被意外裁掉。
频率和深度:中频,浅层展开。它的价值是:为了少量额外信息,避免跳到一个新页面。但只要展开内容长到需要多屏滚动,手风琴就不再是在节省一次跳转——它是在把一个真正的页面藏进一个不适合承载页面的组件里。
10. 长按上下文菜单
没有可见触发器。用户长按某个对象,短暂延迟后,一个浮动菜单出现在手指附近。
<div class="p10-card mono" id="p10Card">Hold this card ↓</div>
<div class="p10-ctx" id="p10Ctx" style="top: 180px; left: 60px">
<button>Copy</button>
<button>Forward</button>
<button>Save</button>
</div>
let pressTimer;
function showCtx() {
p10Ctx.classList.add("show");
p10Card.classList.remove("pressed");
}
function startPress() {
p10Card.classList.add("pressed");
pressTimer = setTimeout(showCtx, 480);
}
function cancelPress() {
clearTimeout(pressTimer);
p10Card.classList.remove("pressed");
}

480ms 接近大多数平台对长按形成的非正式约定。短一点,用户只是慢点点击,也可能误触菜单;长一点,交互会变得像没反应,而不是像一个明确动作。
频率和深度:天然低频。这不是缺点,这是它存在的原因。在密集列表里,如果每一行都放一个可见按钮,会消耗太多注意力。长按菜单把这些低频操作藏起来,界面会更干净。但代价也很明显:可发现性几乎为零。如果某个功能藏在长按里,使用数据很低,不一定说明功能没价值——也可能只是没人发现它。
11. 全屏覆盖菜单
点击菜单触发器后,整个屏幕被导航视图替换,通常有大字号和更宽松的间距。
<div class="p11-overlay" id="p11Overlay">
<div class="p11-top">
<button class="icon-btn" id="p11Close">...</button>
</div>
<ul class="p11-links">
<li><a href="#" class="active"><span class="n">01</span>New arrivals</a></li>
<li><a href="#"><span class="n">02</span>Womenswear</a></li>
<li><a href="#"><span class="n">03</span>Menswear</a></li>
</ul>
</div>
.p11-overlay {
position: absolute;
inset: 0;
z-index: 30;
transform: translateY(-100%);
transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}
.p11-overlay.open {
transform: translateY(0);
}

注意,它是沿 Y 轴滑入,而汉堡抽屉是沿 X 轴滑入。这不是随便选的。侧边抽屉像是在内容旁边揭开一层东西;全屏帘幕从顶部落下,则意味着:现在进入完整聚焦状态,别的内容都先让开。
频率和深度:低频,但不是深层。这是它有趣的地方。它适合 5 到 8 个浅层目的地,不是隐藏很深的账号设置,只是这些入口需要更强的视觉仪式感。当品牌希望“导航本身也被认真设计过”时,全屏覆盖菜单很有效。但如果用户一场会话要在几个 section 之间来回切几十次,每次都全屏接管,就会非常烦。
12. 舵形浮动工具栏
一个小型浮动胶囊,通常包含 2 到 3 个项目。悬在屏幕底部中央,而不是固定占据某条边。
<div class="p12-rudder" id="p12Bar">
<button class="p12-item active" data-p12>...</button>
<button class="p12-item mid" data-p12>...</button>
<button class="p12-item" data-p12>...</button>
</div>
.p12-rudder {
position: absolute;
bottom: 18px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 6px;
border-radius: 30px;
box-shadow: 0 16px 32px -12px rgba(0, 0, 0, 0.6);
z-index: 20;
}
.p12-item.mid {
background: var(--accent);
color: var(--ink);
width: 42px;
height: 42px;
}

这个名字很贴切——它就像内容上的一个小舵。 position: absolute 和阴影负责把它从内容里分离出来,而不是像底部 Tab Bar 那样固定占用一整行布局空间。这对全屏图片、视频、地图等沉浸式内容尤其重要,因为那里每一像素的垂直空间都很贵。
频率和深度:高频,浅层。它和底部 Tab Bar 的使用画像很接近,但它被选择的理由不同:底下的内容需要呼吸。不过,它不适合长文本阅读页面,因为浮动胶囊迟早会挡住用户要看的内容。而且和固定底栏不同,它不会为自己预留空间,也就无法避免这种碰撞。
这十二种模式到底该怎么选?其实,根本的判断标准只有两个:用户多久需要它?它在结构里有多深?
回看一遍,你会发现这些模式天然地对应着不同的使用场景。高频且浅层的入口应该交给底部 Tab Bar、舵形浮动工具栏,以及那些不是真导航的分段控制器。低频且深层的东西才适合塞进汉堡抽屉或部分全屏覆盖菜单。像 Overflow 菜单和长按上下文菜单,它们绑定的是单个对象,而不是整体导航;网格菜单则适用于目的地之间没有真实层级的情况;底部 Sheet 和手风琴列表在原地展开内容,不会把用户送去新页面;侧边栏导航需要常驻可见,但只在更宽视口上才值得付出宽度成本;而 FAB 快捷操作菜单,是一个主操作带着几个紧密相关的子操作。
这些判断,和截图里哪个更好看关系不大。汉堡菜单经常被骂,很多时候也确实该骂,但它的问题不是“汉堡菜单天生坏”——真正的问题是,有人把高频入口塞进了低频模式里。如果只是放那些一场会话点两次的东西,汉堡抽屉依然是完全正确的选择。不管多少文章说 Tab Bar 才是现代答案,它也不该替代所有东西。
说到底,如果连入口的使用频率和结构深度都没想清楚,再花哨的产品设计也只是在错误的框架上涂脂抹粉。别先问“这个菜单好不好看”,先问:用户多久会点?它该放多深?模式应该在这两个答案之后出现,而不是在它们之前。