博客用的是 Hugo 的 PaperMod 主题,默认的导航栏在手机上是横向滚动的:菜单项一多就溢出屏幕,想看后面的得用手指拖着划,很难受。我希望把它改成常见的「点一下汉堡按钮弹出菜单」的形式,顺手还要让导航栏吸顶、点空白处能收回。这篇记录一下完整的改法。
PaperMod 默认的导航结构
先看主题的头部模板 themes/PaperMod/layouts/_partials/header.html,结构大概是:
| |
窄屏时 PaperMod 的处理方式是让 .menu 内部横向滚动:
| |
这就是「要拖动」的根源。PaperMod 没有内置汉堡菜单。
改法:覆盖头部模板,加一个 checkbox
不想直接改主题文件(升级主题会被覆盖),所以把主题的 _partials/header.html 复制到站点的 layouts/partials/header.html 同名覆盖(Hugo 站点目录优先于主题目录),然后在原结构上动三处:
顺带弄清了一个约定:主题把自带的 header/footer 等模板放在
_partials/(下划线前缀表示"主题内部"),站点通常用标准目录partials/。Hugo 会把两边的partials/和_partials/合并进同一个 partial 命名空间(调用时都不带目录前缀,直接partial "header.html"),项目优先于主题——所以放在站点partials/下的同名文件照样能盖掉主题_partials/里的。只要站点内部两个目录别放同名文件就行。
- 在
.logo之后插一个隐藏的<input type="checkbox" id="menu-toggle">; - 在
<ul class="menu">后面加一个<label for="menu-toggle" class="menu-toggle">(画三条横线); - 文件末尾加几行 JS,实现「点空白处收回」。
| |
这里有个很关键的坑:checkbox 必须和 .menu 是同一个父元素下的兄弟,CSS 才能用通用兄弟选择器 :checked ~ .menu 选中菜单。我第一次把它随手塞进 .logo-switches 里,展开规则怎么都不生效,折腾半天才发现是结构位置不对。
汉堡按钮的摆放也有讲究:放 .logo 里会挤在标题旁边,想要贴最右,就得放到菜单后面,space-between 布局会把它自动推到右端。
CSS:900px 以下折叠,点按钮展开
汉堡按钮、三横线的基础样式,以及折叠/展开规则都写在站点的 assets/css/extended/custom.css 里。extended 目录下的 CSS 会被 Hugo 自动合并到主题样式之后,天然拥有覆盖主题规则的优先级:
| |
li + li 是「给除了第一个以外的每个菜单项顶上画条虚线」,比给每个 li 都画、再单独去掉第一个的写法干净。
为什么不用 JS 做展开?
常见的做法是 JS 切换 class。但这里其实只需要一个隐藏 checkbox 就能完成「点一下开、再点一下关」:label 点击会原生切换 checkbox 的 checked 状态,:checked 时 CSS 展开菜单,完全不依赖 JS,也没有多余的 class 管理。这个「checkbox hack」在移动端菜单、折叠面板里都很好用。
它做不到的只有一件事:点击菜单以外的空白处自动收回——checkbox 只能被自己的 label 切换,管不到页面其它位置的点击。这个需求就交给 JS 兜底,写在 header 模板末尾:
| |
e.target.closest('.header') 用来排除导航栏内部:如果点的是汉堡按钮(label),此时 checkbox 刚被切换,不该再被这个监听器改回去;点悬浮面板里的链接则直接跳转,页面刷新后状态自然复位。只监听「点在 .header 之外」的情况,就不会和按钮行为打架。
面板外观:几轮微调
菜单展开的形态也调了几版,值得记一下过程:
- 最初是文档流展开:菜单显示在导航栏下方第二行,会把导航栏撑高,视觉上是「导航栏长高了一块」,还和吸顶效果打架;
- 改成绝对定位悬浮:
position: absolute脱离文档流,面板悬在导航栏正下方 6px 处,盖在正文上方,导航栏高度稳定不跳动。配合z-index: 110保证盖住正文; - 宽度贴内容:一开始面板左右拉满屏幕(
left: 0; right: 0),手机上显得空旷;改固定280px又会在菜单项少时留白。最终用width: max-content让宽度刚好包住最长的菜单项,min-width: 120px保证点击区域别太窄,max-width兜底超窄屏不溢出; - 位置靠右:面板
right: var(--gap),右缘和汉堡按钮对齐,从按钮正下方落下来,符合手指操作习惯。
配色上直接复用主题变量:background: var(--entry) 是比页面底色略亮的卡片色(我博客的纸张色体系里 entry 就是浅一档的纸色),--border 画边框,阴影用了偏暖的半透明黑 rgba(60, 55, 45, 0.18),浅色深色主题下都协调,无需为暗色单独写。
相关的小改动
这次顺带做了几处配套调整,一并记录:
- 导航栏吸顶:默认导航栏滚出屏幕就没了。给
.header加position: sticky; top: 0; z-index: 100; background: var(--theme)。注意要给背景色,否则正文滚到导航栏下方会透出来; - 文章页侧栏避让:文章页右侧的目录/标签栏原本
sticky top: var(--gap),导航栏吸顶后会被挡住,改成top: calc(var(--header-height) + var(--gap)),正好落在导航栏之下; - 移动端侧边卡片:文章页的分类/标签/系列卡片在窄屏原本
display: none,我把 HTML 移到正文之后、CSS 默认改为文档流展示(只有.wide时才绝对定位到右侧),手机端文章读完就能在底部看到这几张卡片。
涉及文件
| 文件 | 作用 |
|---|---|
layouts/partials/header.html | 覆盖主题头部模板,加 checkbox、汉堡按钮、关闭脚本 |
assets/css/extended/custom.css | 汉堡按钮、折叠/悬浮面板、导航栏吸顶样式 |
断点选在 900px,和 PaperMod 自带的窄屏断点一致,超过 900px 一切恢复桌面横排。就这样,手机上终于不用再拖着导航栏滑了。