
本文介绍如何通过监听 url 哈希(`#`)变化与导航点击事件,动态显示对应内容区块并维护 `active` 状态,确保页面加载和用户交互时内容展示一致、哈希同步、样式准确。
在构建单页应用式网站(如学术项目主页)时,常需用 <nav> 导航栏控制多个 <div> 内容区块的显隐,同时保持 URL 哈希(如 #About)与当前视图同步——这不仅提升用户体验,还支持浏览器前进/后退、书签直连等关键功能。
核心逻辑分为两部分:用户点击导航时更新哈希并激活对应区块,以及页面加载/哈希变化时初始化或响应状态。原代码的主要问题在于:
- 在点击事件中错误地混用了初始哈希 pathName(该值在页面加载时固定,未随点击实时更新);
- 缺少对哈希变更的监听(如用户手动修改地址栏或使用浏览器历史导航);
- 激活逻辑冗余且条件冲突(如同时判断 div.id === targetDiv 和 div.id === pathName,但 pathName 并未在点击后刷新)。
以下是优化后的完整实现:
const navLinks = document.querySelectorAll(".nav-links a");
const divs = document.querySelectorAll("div.articleDiv");
// 处理导航点击:阻止默认跳转、更新哈希、激活目标区块
navLinks.forEach(link => {
link.addEventListener("click", e => {
e.preventDefault();
const targetId = link.getAttribute("href").replace("#", "");
// 更新 URL 哈希(触发 hashchange)
window.location.hash = targetId;
// 激活目标 div,隐藏其余
divs.forEach(div => {
if (div.id === targetId) {
div.classList.add("active");
} else {
div.classList.remove("active");
}
});
});
});
// 页面加载时:根据初始哈希或默认值激活对应区块
function activateSection() {
const hash = window.location.hash.replace("#", "");
divs.forEach(div => div.classList.remove("active")); // 先清空所有 active
if (hash && document.getElementById(hash)) {
document.getElementById(hash).classList.add("active");
} else {
document.getElementById("Home").classList.add("active"); // 默认首页
}
}
// 首次加载 + 哈希变化时均触发
window.addEventListener("load", activateSection);
window.addEventListener("hashchange", activateSection);? 关键改进说明:
- 使用 window.location.hash = targetId 显式设置哈希,既触发浏览器地址栏更新,又确保 hashchange 事件可被监听;
- 将激活逻辑抽离为独立函数 activateSection(),复用于 load 和 hashchange 事件,避免重复代码与状态不一致;
- 每次激活前先清除所有 active 类,再精准添加,杜绝残留样式干扰;
- 增加 document.getElementById(hash) 存在性校验,防止因哈希值无效(如拼写错误)导致脚本报错。
? 额外建议:
- 为提升可访问性,可在激活 div 后调用 div.focus()(需确保 div 有 tabindex="0");
- 若内容区块较多,可配合 CSS 过渡(如 transition: opacity 0.3s)实现淡入效果;
- 生产环境推荐使用 History API 替代哈希路由以获得更 clean 的 URL,但哈希方案对静态站点仍简洁高效。
此方案结构清晰、职责分明,兼顾首次加载、用户点击、历史导航三种场景,是轻量级单页导航的可靠实践。










