导航栏模块升级与新增功能

2021 字
10 分钟
导航栏模块升级与新增功能
AI 概括

点击按钮,AI 将为你生成这篇文章的摘要

目录#


一、概述#

本次对博客导航栏模块进行了五项核心改进,对标参考博客 blog.tsh520.cn 的导航栏实现,全面提升导航栏的功能性和用户体验:

改进项说明优先级
分类独立页面新增 /categories/ 页面,与归档分离
hoverTitle鼠标悬停 Logo 时标题变化为趣味文字
点击触发下拉桌面端下拉菜单从 hover 改为 click
Guestbook 统一移除重复顶级入口,仅保留在子菜单
枚举扩展LinkPreset 新增 Categories/Books 等

二、五层架构总览#

导航栏系统由五层架构组成:

类型层 (config.ts) → 定义 TypeScript 类型和枚举
配置层 (navBarConfig.ts) → 动态生成导航链接
常量层 (link-presets.ts) → 预设链接映射
组件层 (Navbar.astro 等) → 渲染 UI 组件
样式层 (navbar.css / main.css) → 视觉呈现

三、改进一:分类独立页面#

3.1 背景#

原项目只有归档页面(/archive/),分类信息通过 CategoryBar 横向滚动条展示。新增独立分类页面,提供更直观的分类浏览体验。

3.2 新增页面 src/pages/categories.astro#

---
import I18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation";
import { Icon } from "astro-icon/components";
import MainGridLayout from "@layouts/MainGridLayout.astro";
import { getCategoryList } from "@/utils/content-utils";
const categories = await getCategoryList();
const totalCategories = categories.length;
const totalPosts = categories.reduce((sum, cat) => sum + cat.count, 0);
---
<MainGridLayout title={i18n(I18nKey.categories)}>
<div class="card-base p-6 md:p-8 onload-animation">
<!-- 标题区域 -->
<div class="flex items-center gap-3 mb-6">
<div class="flex items-center justify-center w-12 h-12
rounded-xl bg-(--primary) text-white">
<Icon name="material-symbols:folder-open" class="text-[1.5rem]" />
</div>
<div>
<h1 class="text-2xl font-bold text-black/90 dark:text-white/90">
{i18n(I18nKey.categories)}
</h1>
<p class="text-sm text-black/50 dark:text-white/50">
{totalCategories} 个分类,{totalPosts} 篇文章
</p>
</div>
</div>
<!-- 分类网格 -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{categories.map((cat) => (
<a
href={`/archive/?category=${encodeURIComponent(cat.name)}`}
class="group card-base p-4 transition-all duration-200
hover:translate-y-[-2px] hover:shadow-lg"
>
<div class="flex items-center gap-3">
<div class="flex items-center justify-center w-10 h-10
rounded-lg bg-(--primary)/10 text-(--primary)
group-hover:bg-(--primary) group-hover:text-white
transition-all duration-200">
<Icon name="material-symbols:folder" class="text-[1.25rem]" />
</div>
<div class="flex-1 min-w-0">
<h3 class="font-bold text-black/80 dark:text-white/80
truncate group-hover:text-(--primary) transition-colors">
{cat.name}
</h3>
<p class="text-xs text-black/40 dark:text-white/40">
{cat.count} 篇文章
</p>
</div>
<Icon
name="material-symbols:chevron-right-rounded"
class="text-lg text-black/20 dark:text-white/20
group-hover:text-(--primary) transition-all
group-hover:translate-x-1"
/>
</div>
</a>
))}
</div>
</div>
</MainGridLayout>

3.3 数据获取#

分类数据通过 src/utils/content-utils.ts 中的 getCategoryList() 获取:

export type Category = {
name: string;
count: number;
url: string;
};
export async function getCategoryList(): Promise<Category[]> {
const allBlogPosts = await getCollection<"posts">("posts", ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
});
// ... 按分类统计文章数量并排序
}

3.4 导航栏入口#

src/config/navBarConfig.ts 中添加分类链接:

const links: (NavBarLink | LinkPreset)[] = [
LinkPreset.Home,
LinkPreset.Categories, // ← 新增
LinkPreset.Archive,
// ...
];

四、改进二:hoverTitle 标题变化#

4.1 功能说明#

鼠标悬停在导航栏 Logo 区域时,标题从正常文字变为趣味文字,鼠标移开后恢复。参考博客使用此功能增加趣味交互。

4.2 类型定义#

src/types/config.tsSiteConfig.navbar 中新增 hoverTitle 字段:

navbar: {
logo?: {
type: "icon" | "image" | "url";
value: string;
alt?: string;
};
title?: string;
hoverTitle?: string; // ← 新增:鼠标悬停时显示的标题
widthFull?: boolean;
menuAlign?: "left" | "center";
followTheme?: boolean;
stickyNavbar?: boolean;
};

4.3 配置使用#

src/config/siteConfig.ts 中设置:

navbar: {
logo: {
type: "image",
value: "assets/images/firefly.png",
alt: "🍀",
},
title: "lyf:Blog",
hoverTitle: "👋 别走嘛,再看看!", // ← 新增
// ...
},

4.4 组件实现#

src/components/layout/Navbar.astro 中:

模板部分 — 添加 data 属性:

<div id="navbar" class="z-50"
data-hover-title={navbarHoverTitle}
...>
<a href={url('/')} class="btn-plain ... navbar-logo-link"
data-original-title={navbarTitle}>
<!-- Logo + 标题 -->
</a>
</div>

脚本部分 — 添加事件监听:

function initHoverTitle() {
const navbar = document.getElementById('navbar');
if (!navbar) return;
const hoverTitle = navbar.getAttribute('data-hover-title');
if (!hoverTitle) return;
const logoLink = navbar.querySelector('.navbar-logo-link');
if (!logoLink) return;
const logoText = logoLink.querySelector('span, div');
if (!logoText) return;
logoLink.addEventListener('mouseenter', function() {
const currentText = logoText.textContent;
logoText.setAttribute('data-text', currentText || '');
logoText.textContent = hoverTitle;
});
logoLink.addEventListener('mouseleave', function() {
const originalText = logoText.getAttribute('data-text');
if (originalText) {
logoText.textContent = originalText;
}
});
}
initHoverTitle();
document.addEventListener('astro:page-load', initHoverTitle);
document.addEventListener('swup:contentReplaced', initHoverTitle);

4.5 自定义方法#

修改 hoverTitle 值即可自定义:

siteConfig.ts
hoverTitle: "✨ 发现更多精彩内容!", // 趣味风格
hoverTitle: "w(゚Д゚)w 不要走!", // 惊讶风格
hoverTitle: "🎉 欢迎回来!", // 欢迎风格

五、改进三:下拉菜单点击触发#

5.1 背景#

原实现使用 CSS :hover 触发下拉菜单,存在以下问题:

  • 移动端/触屏设备无法使用
  • 鼠标移出菜单区域时容易误关
  • 与无障碍访问冲突

改为点击触发(.dropdown-open 类切换),与参考博客一致。

5.2 CSS 变更#

src/styles/main.css — 全局样式修改:

/* 旧:hover 触发 */
.dropdown-container:hover .dropdown-menu,
.dropdown-container:focus-within .dropdown-menu {
@apply opacity-100 visible pointer-events-auto translate-y-0;
}
/* 新:click 触发 */
.dropdown-container.dropdown-open .dropdown-menu,
.dropdown-container:focus-within .dropdown-menu {
@apply opacity-100 visible pointer-events-auto translate-y-0;
}

src/components/layout/DropdownMenu.astro — 组件内样式同步修改:

/* 新:点击触发 */
.dropdown-container.dropdown-open .dropdown-menu {
@apply opacity-100 visible pointer-events-auto translate-y-0;
}
.dropdown-container.dropdown-open .dropdown-arrow {
@apply rotate-180;
}

5.3 JavaScript 逻辑#

function initDropdowns() {
const dropdowns = document.querySelectorAll('[data-dropdown]');
dropdowns.forEach(dropdown => {
const trigger = dropdown.querySelector('[data-dropdown-trigger]');
if (!trigger) return;
// 克隆节点清除旧事件监听器
const newTrigger = trigger.cloneNode(true);
trigger.parentNode?.replaceChild(newTrigger, trigger);
// 点击触发器 → 切换 .dropdown-open 类
newTrigger.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
dropdown.classList.toggle('dropdown-open');
const isOpen = dropdown.classList.contains('dropdown-open');
newTrigger.setAttribute('aria-expanded', String(isOpen));
});
// Escape 键关闭
newTrigger.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
dropdown.classList.remove('dropdown-open');
newTrigger.setAttribute('aria-expanded', 'false');
newTrigger.focus();
}
});
});
}
// 点击外部关闭所有下拉菜单
document.addEventListener('click', function(e) {
const dropdowns = document.querySelectorAll('[data-dropdown]');
dropdowns.forEach(dropdown => {
if (!dropdown.contains(e.target)) {
dropdown.classList.remove('dropdown-open');
}
});
});

5.4 交互行为#

操作行为
点击有子菜单的导航项打开下拉菜单
再次点击关闭下拉菜单
点击页面其他区域关闭所有下拉菜单
按 Escape 键关闭所有下拉菜单并聚焦回触发器
点击子菜单项跳转链接并关闭菜单

六、改进四:Guestbook 入口统一#

6.1 变更说明#

改前:Guestbook 同时作为顶级链接 + “动态”子菜单项出现

主页 | 归档 | 网站导航 | 留言板 | 动态(说说/相册/留言板) | ...

改后:仅保留在”动态”子菜单中

主页 | 分类 | 归档 | 网站导航 | 动态(说说/相册/留言板) | ...

6.2 代码变更#

src/config/navBarConfig.ts 中移除顶级链接:

// 根据配置决定是否添加留言板
if (siteConfig.pages.guestbook) {
links.push(LinkPreset.Guestbook);
}

“动态”子菜单中保持不变:

children: [
...(siteConfig.pages.talks ? [LinkPreset.Talk] : []),
...(siteConfig.pages.gallery ? [LinkPreset.Gallery] : []),
...(siteConfig.pages.guestbook ? [LinkPreset.Guestbook] : []), // 保留
],

七、改进五:LinkPreset 枚举扩展#

7.1 变更说明#

为常用页面添加独立的 LinkPreset 枚举值,保持与参考博客一致:

src/types/config.ts
export enum LinkPreset {
Home = 0,
Archive = 1,
About = 2,
Friends = 3,
Sponsor = 4,
Guestbook = 5,
Bangumi = 6,
Gallery = 7,
Talk = 8,
Categories = 9, // ← 新增
Books = 10, // ← 新增
MoviesGames = 11, // ← 新增
MusicPage = 12, // ← 新增
Changelog = 13, // ← 新增
}

7.2 预设映射#

src/constants/link-presets.ts 中添加映射:

[LinkPreset.Categories]: {
name: i18n(I18nKey.categories),
url: "/categories/",
icon: "material-symbols:folder-open",
},
[LinkPreset.Books]: {
name: "书架",
url: "/books/",
icon: "material-symbols:book-5",
},
[LinkPreset.MoviesGames]: {
name: "影视与游戏",
url: "/movies-games/",
icon: "material-symbols:movie",
},
[LinkPreset.MusicPage]: {
name: i18n(I18nKey.music),
url: "/music/",
icon: "material-symbols:music-note",
},
[LinkPreset.Changelog]: {
name: "更新日志",
url: "/changelog/",
icon: "material-symbols:history",
},

7.3 使用方式#

扩展后的枚举可在 navBarConfig.ts 中直接引用:

// 使用预设(推荐)
links.push(LinkPreset.Books);
// 也可继续使用自定义 NavBarLink
links.push({
name: "书架",
url: "/books/",
icon: "material-symbols:book-5",
});

八、导航栏最终结构#

Navbar.astro ← 主容器
├── Logo 区域 (hoverTitle 支持)
│ ├── Icon / Picture / Image
│ └── 标题文字 (mouseenter 变化)
├── 桌面端导航链接 (.hidden.lg:flex)
│ ├── 主页 → /
│ ├── 分类 → /categories/ ← 新增
│ ├── 归档 → /archive/
│ ├── 网站导航 → /projects/
│ ├── 动态 (点击触发下拉) ← 改为 click
│ │ ├── 说说
│ │ ├── 相册
│ │ └── 留言板
│ ├── 记录 (点击触发下拉)
│ │ ├── 书架
│ │ ├── 影视与游戏
│ │ ├── 音乐
│ │ ├── 更新日志
│ │ ├── 规划
│ │ └── 足迹
│ └── 关于 (点击触发下拉)
│ ├── 关于我
│ ├── 友链
│ └── 赞助
├── 右侧工具栏
│ ├── 搜索 (Search.svelte)
│ ├── 音乐按钮
│ ├── 管理后台
│ ├── 显示设置
│ ├── 亮暗切换
│ └── 汉堡菜单 (移动端)
├── 移动端侧滑菜单 (NavMenuPanel.astro)
└── 显示设置面板

九、文件变更清单#

文件操作说明
src/types/config.ts修改LinkPreset 新增 5 个枚举值 + navbar.hoverTitle 类型
src/constants/link-presets.ts修改新增 5 个预设映射
src/config/siteConfig.ts修改新增 hoverTitle 配置
src/config/navBarConfig.ts修改添加分类链接 + 移除顶级 Guestbook
src/components/layout/Navbar.astro修改hoverTitle data 属性 + 事件脚本
src/components/layout/DropdownMenu.astro修改CSS hover→click + 脚本重写
src/styles/main.css修改全局下拉菜单 CSS 改为 click 触发
src/pages/categories.astro新建分类独立页面

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
导航栏模块升级与新增功能
https://f3f3.top/posts/p6c6a87df/
作者
lyf
发布于
2026-05-29
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
lyf
Hello, I'm LyF.
公告
欢迎来到一飞的博客!。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
14
分类
5
标签
16
总字数
48,064
运行时长
0
最后活动
0 天前

文章目录

🤖 AI 助手

👋 你好!

我可以帮你解答关于这篇文章的问题