
隐藏导航代码怎么写:用 CSS 和 JavaScript 实现导航栏显示与收起
导航栏需要隐藏或收起,常见于手机端菜单、页面滚动时缩小顶部导航,以及需要腾出屏幕空间的阅读界面。实现方式看起来只是切换一个 CSS 类,实际还要考虑键盘操作、屏幕阅读器、窗口尺寸变化和无 JavaScript 时的表现。
本文先给出一份可直接运行的响应式示例,再解释不同实现方式的适用场景、参数边界和常见问题。示例中的“隐藏”只影响页面显示,不构成访问控制。
先区分几种“隐藏”需求
- 展开/收起菜单:用户点击按钮后显示或隐藏导航链接。适合手机端菜单,也可用于较窄的侧栏。
- 滚动时隐藏顶部栏:向下滚动时收起导航,向上滚动时重新显示。适合内容阅读页面,但不适合必须始终可访问的主导航。
- 滚动到某个位置后隐藏:根据页面滚动距离显示或隐藏导航。适合需要在首屏展示内容、越过首屏后再收起的页面。
- 根据页面状态隐藏:例如打开对话框时暂时隐藏背景导航。此时还需要处理焦点和遮罩,不能只把导航设为不可见。
下面的主示例实现第一种需求:宽屏时导航始终显示;窄屏时显示“菜单”按钮,点击后展开或收起导航。断点、颜色和间距都是示例值,需要按项目设计调整。
可运行示例:响应式导航展开与收起
将以下内容保存为 index.html,使用浏览器打开即可运行。示例使用原生 HTML、CSS 和 JavaScript,不依赖第三方库。代码中的脚本放在页面末尾,因此可以直接获取前面的元素。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>响应式导航示例</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
color: #222;
}
.site-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1rem 1.25rem;
background: #17324d;
color: #fff;
}
.site-name {
color: inherit;
font-weight: 700;
text-decoration: none;
white-space: nowrap;
}
.menu-toggle {
display: none;
padding: 0.55rem 0.8rem;
border: 1px solid currentColor;
border-radius: 0.35rem;
background: transparent;
color: inherit;
font: inherit;
cursor: pointer;
}
.site-nav ul {
display: flex;
align-items: center;
gap: 1.25rem;
margin: 0;
padding: 0;
list-style: none;
}
.site-nav a {
color: inherit;
text-decoration: none;
}
.site-nav a:hover,
.site-nav a:focus-visible {
text-decoration: underline;
}
main {
max-width: 60rem;
margin: 2rem auto;
padding: 0 1.25rem;
line-height: 1.7;
}
@media (max-width: 48rem) {
.menu-toggle {
display: inline-block;
}
.site-header {
flex-wrap: wrap;
}
.site-nav {
flex-basis: 100%;
}
.site-nav ul {
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
padding: 0.5rem 0 0.25rem;
}
/* JavaScript 已启用且菜单未展开时,才在窄屏隐藏导航。 */
.js-ready .site-nav:not(.is-open) {
display: none;
}
}
</style>
</head>
<body>
<header class="site-header">
<a class="site-name" href="/">示例网站</a>
<button
class="menu-toggle"
type="button"
aria-controls="primary-navigation"
aria-expanded="false"
hidden
>
菜单
</button>
<nav
class="site-nav"
id="primary-navigation"
aria-label="主导航"
>
<ul>
<li><a href="#intro">介绍</a></li>
<li><a href="#services">服务</a></li>
<li><a href="#contact">联系</a></li>
</ul>
</nav>
</header>
<main>
<h1 id="intro">页面内容</h1>
<p>调整浏览器宽度,或在手机尺寸下点击“菜单”按钮,观察导航的展开与收起。</p>
<h2 id="services">服务</h2>
<p>这里放置页面主体内容。</p>
<h2 id="contact">联系</h2>
<p>这里放置联系信息。</p>
</main>
<script>
const toggle = document.querySelector(".menu-toggle");
const nav = document.querySelector("#primary-navigation");
const mobileQuery = window.matchMedia("(max-width: 48rem)");
function setMenuOpen(open) {
nav.classList.toggle("is-open", open);
toggle.setAttribute("aria-expanded", String(open));
}
function syncMenuForViewport() {
if (mobileQuery.matches) {
toggle.hidden = false;
setMenuOpen(false);
} else {
toggle.hidden = true;
nav.classList.remove("is-open");
toggle.setAttribute("aria-expanded", "false");
}
}
toggle.addEventListener("click", () => {
const isOpen = toggle.getAttribute("aria-expanded") === "true";
setMenuOpen(!isOpen);
});
mobileQuery.addEventListener("change", syncMenuForViewport);
// 先注册交互,再启用依赖 JavaScript 的窄屏隐藏样式。
document.body.classList.add("js-ready");
syncMenuForViewport();
</script>
</body>
</html>
代码的工作方式
CSS 负责布局,JavaScript 负责状态
媒体查询 @media (max-width: 48rem) 决定窄屏布局:导航链接改为纵向排列,按钮显示。导航是否收起,则由 .is-open 类控制。JavaScript 不直接拼接样式字符串,而是切换类名,便于把展示规则集中在 CSS 中维护。
这里的 48rem 是响应式断点示例,不是浏览器规定的特殊值。应根据导航项长度、字体大小和布局实际效果确定断点。由于 rem 与根元素字号相关,它在多数默认设置下约为 768 CSS 像素,但用户调整默认字号时对应的像素宽度也会变化。若项目需要固定 CSS 像素断点,可以使用例如 48em 或 768px,并在目标浏览器中验证。
按钮与导航的关系要明确
按钮的 aria-controls="primary-navigation" 指向受它控制的导航区域;aria-expanded 表示当前展开状态。脚本在每次展开、收起时同步更新该属性,辅助技术才能获得一致的状态信息。导航上的 aria-label 用于说明这组链接的用途;如果页面有多个导航区域,应使用不同且有意义的名称。
type="button" 可以避免按钮位于表单中时意外触发表单提交。按钮使用原生 <button>,默认支持键盘操作;不要为了外观改成没有键盘行为的普通 <div>。
为什么脚本启用后才隐藏导航
示例中的导航默认可见,按钮默认带有 hidden。JavaScript 初始化完成后才给 body 加上 js-ready 类,窄屏 CSS 才会在菜单关闭时隐藏导航。这样即使脚本未加载或执行失败,导航链接仍可见、可用,而不是只剩一个无法工作的按钮。
脚本还监听媒体查询变化。用户旋转手机、调整窗口宽度或使用分屏时,视口可能跨过断点。进入窄屏后菜单重置为关闭;回到宽屏后按钮隐藏、导航由桌面 CSS 显示。这个处理避免了只在页面首次加载时判断宽度,导致调整窗口后状态不一致的问题。
只用 CSS 能不能隐藏导航
可以。使用媒体查询控制桌面与移动布局,或用复选框配合 :checked 展开菜单,都能实现纯 CSS 效果。但复选框方案需要处理标签点击区域、焦点样式和语义;媒体查询只能响应样式条件,无法根据用户点击、导航选择或业务状态更新 ARIA 属性。对于需要清晰交互状态的菜单,按钮加 JavaScript 通常更容易维护。
如果菜单只需要在窄屏展开,优先使用上面的渐进增强方式,而不是无条件隐藏导航。若页面要求不依赖脚本也能正常使用,应保证导航在脚本不可用时保持可见,或使用服务器渲染、组件框架的状态管理实现等价行为。
滚动时自动隐藏:适用场景与实现注意点
另一类常见需求是向下滚动时隐藏固定顶部栏,向上滚动时显示。它可能减少遮挡,但也会让用户在阅读过程中暂时找不到导航,不适合导航承担重要操作或页面切换任务的场景。若采用这种设计,应提供明确的重新显示方式,并在触屏、键盘和辅助技术操作下验证。
实现时通常比较当前滚动位置与上一次位置,再根据滚动方向切换类名。不要在每个滚动事件中执行大量布局读写;页面较复杂时,可用 requestAnimationFrame 合并更新,并避免频繁改变会触发布局的属性。滚动方向判断需要设置“抖动区间”:用户轻微滑动或触控板噪声不应让导航反复闪烁。这个区间没有通用的正确值,需要按内容密度和设备测试。
如果只需滚过首屏后隐藏,可以用固定阈值判断,例如滚动超过首屏高度后隐藏;阈值应来自实际设计,而不是随意假定所有设备首屏高度相同。窗口尺寸变化、浏览器地址栏伸缩和页面缩放都可能影响可视区域,因此应根据目标效果选择 window.innerHeight、具体元素高度或 CSS 媒体查询,并验证移动端行为。
无论采用哪种滚动方案,改变显示状态时都要保持视觉与无障碍状态一致。不要把焦点留在刚刚隐藏的链接上;如果用户正通过键盘操作导航,不宜因为一次滚动事件突然将其藏起。动画可选,但应尊重 prefers-reduced-motion,为偏好减少动态效果的用户关闭或简化过渡。
让收起动画更平滑
示例直接切换 display,状态明确、实现简单,但不会产生渐隐动画。CSS 的 display: none 不能像普通数值那样平滑过渡。若需要动画,可以改用 opacity、transform 或尺寸属性,但必须同时处理隐藏后的键盘焦点和可访问性:透明不代表不可交互,移出屏幕也不一定会让元素从辅助技术的阅读顺序中消失。
不要只把导航设为 opacity: 0,就认为它已经收起。隐藏状态还要确保链接不能被键盘误选,且辅助技术不会把它当作仍可操作的内容。可以在动画结束后设置隐藏状态,展开时先恢复可访问状态再启动动画;也可以使用经过验证的动画组件或浏览器支持的离散属性过渡方案。若不需要动画,直接使用显示/隐藏规则往往更稳妥。
常见错误与排查方法
1. 点击后视觉状态变了,屏幕阅读器状态没变
只切换 CSS 类、不更新 aria-expanded,会造成视觉和语义不一致。将状态集中在一个函数里更新,避免在多个事件处理器中分别改类名和属性。示例中的 setMenuOpen 就承担这个职责。
2. 菜单收起后仍能通过 Tab 进入链接
常见原因是只设置透明度、负向定位或变换,没有真正隐藏内容或禁用其交互。确认关闭状态下导航不显示,且其中链接不会获得焦点。使用动画时尤其要检查动画结束前后的焦点行为。
3. 桌面布局正常,窗口缩放后菜单卡在错误状态
如果脚本只在加载时读取一次屏幕宽度,没有监听视口变化,就可能出现桌面导航仍然处于移动端收起状态、或按钮仍然显示的问题。应根据媒体查询变化重新同步状态,或让组件框架在断点变化时重新计算。
4. 按钮没有名称,或使用图标后无法辨认
纯图标按钮应提供可访问名称,例如可见文本、aria-label,或屏幕阅读器可读的文本。若按钮文本会在“菜单”和“关闭”之间变化,也要确保变化与展开状态一致。不要把按钮的名称和 aria-expanded 状态写成互相矛盾的信息。
5. CSS 选择器被其他规则覆盖
检查浏览器开发者工具中的计算样式。全局规则、组件库样式或更高优先级的选择器可能覆盖导航的 display。避免到处添加 !important;先确认样式来源和加载顺序,再调整组件选择器。若使用 HTML 的 hidden 属性,也要留意项目 CSS 是否强行给元素设置了显示样式。
6. 在按钮里放链接,或用点击整个导航容器切换
按钮用于执行展开/收起操作,链接用于导航到目标地址。把链接嵌在按钮中会造成交互语义混乱;给整个导航容器绑定点击事件,也可能导致点击链接时触发展开逻辑。将事件绑定在专用按钮上,并让导航链接保持正常的链接行为。
隐藏导航不等于安全控制
CSS 隐藏、JavaScript 删除节点、折叠菜单或设置 aria-hidden,都不能保护数据或限制用户访问。页面源码、前端脚本和已经发送到浏览器的数据,通常都可通过开发者工具、网络请求或其他客户端方式查看;隐藏界面也不会阻止用户直接请求某个 URL。
如果某个页面、接口或数据需要权限,应在服务器端对每次请求进行身份验证和授权,并且只向有权限的用户返回相应数据。前端隐藏可以改善界面体验,但不能替代服务器端权限检查。也不要把密钥、令牌或不应公开的数据放在前端代码中,再试图通过隐藏导航来保护它们。
上线前检查清单
- 窄屏和宽屏下导航都符合预期,调整窗口大小后状态不会卡住。
- 按钮可通过键盘操作,焦点有清晰可见的样式。
aria-expanded与菜单实际状态同步,aria-controls指向正确的导航元素。- JavaScript 关闭或加载失败时,核心导航仍有可用的兜底方案。
- 关闭状态下,链接不会被误操作或意外读作可用内容。
- 如有动画,验证减少动态效果偏好;如有滚动自动隐藏,验证键盘、触屏和小屏设备。
- 需要保护的页面和数据由服务器端做授权,而不是依赖前端隐藏。
简单导航可以从“CSS 负责布局、JavaScript 只切换状态”开始。把断点、开关状态、无脚本兜底和权限边界分别处理,比堆叠复杂动画或用一条选择器隐藏所有内容更可靠。









