Hexo顶部导航栏魔改
Hexo顶部导航栏魔改
说在前面
本篇文章用于记录本人对网站的顶部导航栏进行魔改的过程,依旧代码部分由AI编写,重要的是思路和过程。
整体思路仍然是在主题的 inject 配置注入CSS 和 JS,不动主题自己的文件,新增的东西都放在 source/ 和 scripts/ 目录里。
需求
我习惯先把想要的效果写清楚,不然即使有AI辅助,后面也很容易写着写着就跑偏。
功能需求:
| 位置 | 功能项 |
|---|---|
| 左侧 | 首页、开始浏览、朋友圈、专栏 |
| 中间 | 正在阅读 |
| 右侧 | 目录、搜索、回顶、中控台 |
左边是菜单项,我打算放这四个:首页、开始浏览、朋友圈、专栏。开始浏览页面主要是我的文章分类结构,朋友圈页面用于分享一些其他网站或博主的优质内容导航。专栏则用于导航我网站的一些非文章页面。
右侧是页面的功能按钮,包括搜索功能、文章目录、一键回顶,以及一个中控台按钮,中控台按钮点击后显示一个全屏遮罩,用于展现你想实现的个性化功能。
中控台按钮的想法我也是看了洪哥的网页后,觉得效果非常好,于是自己也打算做一个。张洪Heo - 分享设计与科技生活
中间这块我打算做一个公用的信息位,设计承载三种内容:
正常时显示目前「正在阅读」的文章名称
鼠标悬浮时,显示一个胶囊样式的交互功能按钮,目前暂定是「随机阅读一篇文章」
因为右侧打算用纯图标的样式,需要有悬浮时的提示文字,于是也打算放在这里,鼠标移到右侧图标上时,这里就顺着跳出对应功能的名称。一个位置解决三个功能,节省空间的同时也比较美观。
美术需求:
样式上,我想这样实现,窗口在最顶部时导航栏正常左右分布,背景透明,和 banner 融在一起;当窗口滑动下去时,就收拢成居中的胶囊形状,长度占页面的大约2/3左右,同样半透明加毛玻璃,左右留白,不再是贴满整个屏幕的白条。
动效上,通过这样设计,就可以实现导航栏展开、收拢的效果,会非常好看。这也不是我原创的,之前不记得在哪个博客看到过了,印象深刻,不过实在是找不到了,这里说声抱歉。然后,下滑时隐藏导航栏,把位置让给内容,上滑时再出现,这个是butterfly主题自带的动画了,不用较大改动。最后是各个功能按钮的交互动画,打算暂时先用简单淡入淡出或滑动效果,后面再想想有什么炫酷的招式。
PS: 我后来找到了,原来是一个售卖的商用主题,好看捏,但是要付费。鱼悦未来
实现
实现步骤
动手之前先得把原导航栏摸清楚。Butterfly 的导航写在 themes/butterfly/layout/includes/header/nav.pug,结构很干净,就两块:#blog-info 负责 logo 和站点标题,#menus 里装着搜索按钮、菜单和汉堡按钮。样式在 themes/butterfly/source/css/_layout/head.styl,行为在 themes/butterfly/source/js/main.js。
主题的滚动逻辑已经很完整了 —— 顶部时清掉状态类,往下滚过一定距离加上 nav-fixed,继续下滑去掉 nav-visible 把导航收走,上滑再补回来;#nav 自己的淡入淡出则靠 .show 这个类。也就是说,滚动方向判断、什么时候加类,主题全做好了。新功能只需要让新写的样式去响应 nav-fixed 和 nav-visible 就够了。
最终方案:不改动主题文件,用 inject 注入一份 CSS 和一份 JS;JS 在 #nav 里自己造出左中右三段 DOM,CSS 负责把这些新元素在「顶部展开」和「滚动收拢」两种状态下的几何值分别写清楚。新增内容分散在 source/css、source/js、scripts 三处,生成器产出的数据文件和二级页面交给 Hexo 正常输出。下面按模块说一下各自的思路。
一、三段 DOM(source/js/nav.js)
左侧菜单、中间信息位、右侧功能按钮都由 JS 拼装后追加到 #nav。菜单项刻意保留了主题原有的 site-page 类名,字号、字重、颜色就直接继承主题那套,不用再抄一遍。
2026-9-18更新:
原来的做法在开启
pjax后,点一次站内链接,导航栏内容全空。因为主题的 PJAX 配置,容器选择器里带着#body-wrap,而 原先挂载的header#page-header和nav#nav都在#body-wrap里面,所以每次无刷新跳转,#nav都会被替换成服务器渲染的新节点,JS 注入的内容自然就没了。 原来的写法里,三段 DOM 是脚本执行时一次性建出来的,PJAX 换掉
#nav之后没有任何契机再建一次。最终改法是把结构和事件绑定整体收进一个mount()函数,在pjax:complete时重新执行一遍, 此外,「随机阅读一篇文章」原来是
location.href整页跳转,既然开了 PJAX 就没必要了,改成优先调用主题的pjax.loadUrl(),保持无刷新。
二、样式设计(source/css/nav.css)
把导航高度、离顶距离、圆角、背景、主题色、药丸底色这些抽成一组 CSS 变量,顶部状态和收拢状态就是同一组几何属性的两种取值:顶部时通栏、透明、无圆角,和 banner 融在一起;滚动后左右各让出 16.67%(窗口更窄时放宽),收成居中胶囊,再补上半透明底、毛玻璃、边框和阴影。因为位置、内边距、圆角都在过渡列表里,收拢和展开自然就成了一段动画。下滑隐藏、上滑滑出交给主题的 nav-fixed / nav-visible。
三、中间位置
中间这块要承载三种内容:正在阅读、鼠标悬浮时的「随机阅读」按钮、鼠标移到右侧图标时的功能名。三者要占同一块地方并互相淡入淡出,用 display 切换没法做过渡,用绝对定位又要各自处理尺寸,最后使用grid —— 三个槽位都写 grid-area: 1 / 1 叠在同一格,容器会被撑到最大的那个槽位的尺寸。
四、下拉面板
左侧的「开始浏览」「专栏」和右侧的「查看目录」,交互上其实是同一件事:悬浮在触发器上,下面展开一块面板。所以只写一套通用逻辑,谁需要就带上 nb-drop-wrap 和 data-drop,面板的样式和悬浮行为全部复用。面板里装什么由各自的构建函数决定:目录面板从正文标题现抓,浏览面板渲染 nav.json 的数据。
但是,比如「开始浏览」这里,我想实现一级分类、二级系列的结构,分类部分使用 Hexo 原生categories,二级的系列部分,只是butterfly主题文章 front-matter 里的一个字段,Hexo 根本没有对应的页面。第一反应是给每个系列单独生成一个 /series/<名称>/ 页面,真动手的时候又觉得别扭:等于凭空多出两套长得差不多的列表页,样式还得各维护一份。后来换了想法 —— 二级页面干脆复用一级分类页的布局,通过文章列表的筛选来实现系列的切换。这样一来两级页面外观完全一致,只有右边列表不同,左侧再加一个系列目录来切换。
五、降级
窄屏时完全交还给主题原来的汉堡菜单和侧边栏,手机上那套交互本来就挺好用;另外给关闭动效的用户去掉过渡,给中屏放宽胶囊宽度。
注意事项
悬浮面板要防止触发器和面板之间的缝隙。 鼠标从触发器移到面板上,中间会先离开触发器再进入面板,那一两像素的缝隙就触发了 mouseleave,面板瞬间收起,里面的东西根本点不到。最省事的解法是把面板做成触发器的子节点 —— mouseleave 的判定包含所有后代元素,鼠标就算悬在面板上(已经脱离了触发器本身的盒子)也不算离开;再增加100毫秒左右的延迟防止手抖,点击面板外部或者按 Esc 关闭,不然展开之后只能靠移开鼠标关掉,触屏上会很别扭。
中文路径问题。 静态服务器收到请求时会先把 URL 解码再去找目录,如果使用了中文名,磁盘上的文件夹名字面量就是 %E7%B3%BB...,解码之后就无法找到了,所以 path 用原始名称拼,给前端用的 url 再做 encodeURIComponent,两边分工不同。
尽量复用主题已有代码。 滚动显隐、搜索弹窗、平滑滚动、分类页模板,主题里都已经有了,花十分钟翻一遍源码,比花两小时写一套然后发现和主题打架要划算得多。这次真正自己写的其实只有胶囊样式的造型、中间信息位的三态和系列页的设计,其余都是接上去的。
开了 PJAX 之后,注入的 DOM 必须能被重建。 主题的 pjax 容器里包含 #body-wrap,而 #nav 这类注入目标都在它内部,跳转时会被整块替换成服务器渲染的新节点。所以往这些位置塞 DOM 的模块,都要写成可重复挂载、并在 pjax:complete 里重新执行的形式;反过来,像右键菜单那样直接挂在 body 上的模块完全不受影响,顶多需要跳转时清一下状态(收起菜单、刷新深色和繁简的文案)。
最后,附上这次全部改动的文件目录:
| 文件 | 作用 |
|---|---|
source/css/nav.css |
胶囊造型、展开收拢动效、面板、系列目录、深色模式 |
source/js/nav.js |
三段 DOM、信息位三态、下拉面板、目录/搜索/回顶/随机阅读、系列目录、PJAX 重挂载 |
scripts/nav-data.js |
生成 /nav.json 与二级系列页 |
_config.butterfly.yml |
用 inject 引入上面两个前端文件;开启 pjax |




