更新日志

2026

07-05

完善导航栏教程:将后续使用的 nav-show-title 写法并入正文教程;补充遮罩层 pointer-events 处理,修复标题浮上来时右侧“开往”“随便逛逛”和搜索按钮无法点击的问题

05-22

整理导航栏写法:将滚动切换逻辑调整为 nav-show-title 状态控制,补充 PJAX 场景下的 scroll 监听清理;补充无 rmf.scrollToTop() 时的回顶部写法

前言

美化之前

美化的时候需要修改网站的源文件、添加样式、js之类的,需要一点基础,可以参考

效果

修改效果:导航栏居中,二级菜单横向展示,导航栏显示网站、文章的标题等

警告

右侧的“开往”和“随便逛逛”功能,在后面会有添加

参考链接

修改方案

这一版主要实现几个效果:一级菜单居中、二级菜单横向展开、向下滚动时显示当前页面标题、点击标题回到顶部,同时保留右侧的“开往”“随便逛逛”和搜索按钮。

核心思路很简单:nav.js 只负责根据滚动方向给 #nav 添加或移除 nav-show-title,具体的显示、隐藏和过渡动画都交给 CSS。

1. 修改 nav.pug 文件

修改 [blogRoot]\themes\Butterfly\layout\includes\header\nav.pug

这里在菜单区域里加入 mask-name-containername-container。外层遮罩只负责限制标题动画范围,真正可点击的是里面的 page-name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
nav#nav
span#blog_name
a#site-name(href=url_for('/')) #[=config.title]

#menus
!=partial('includes/header/menu_item', {}, {cache: true})
#mask-name-container
center(id="name-container")
a(id="page-name" href="javascript:scrollToTop()") PAGE_NAME

#toggleButtons
#travellings
a.site-page.social-icon(title="开往-友链接力", href="https://www.travellings.cn/go.html")
i.fa.fa-subway
#randoms
a.site-page.social-icon(onclick='toRandomPost()', title='随便逛逛', href='javascript:void(0);')
i.fa.fa-paper-plane
if (theme.algolia_search.enable || theme.local_search.enable)
#search-button
a.site-page.social-icon.search
i.fas.fa-search.fa-fw
//span=' '+_p('search.title')
#toggle-menu
a.site-page
i.fas.fa-bars.fa-fw

如果你没有右侧“开往”或“随便逛逛”,把对应的 #travellings#randoms 删掉即可。搜索按钮还是跟 Butterfly 原本配置走,开启本地搜索或 Algolia 后才会显示。

2. 添加 nav.js

新建或修改你的自定义 JS,例如 source/static/js/nav.js。如果你还没配置自定义 JS 的引入,可以先看这篇:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* 导航栏滚动切换:向下滚动显示页面标题,向上滚动恢复菜单
* 通过 CSS class 控制状态,避免内联样式污染
*/
;(() => {
'use strict'

const NAV_STATE_CLASS = 'nav-show-title' // 向下滚动时添加到 #nav
const SCROLL_THRESHOLD = 50

let lastScrollTop = 0
let ticking = false
let scrollHandler = null

function updateNav () {
const scrollTop = window.scrollY || document.documentElement.scrollTop
const nav = document.getElementById('nav')
if (!nav) return

if (scrollTop > lastScrollTop && scrollTop > SCROLL_THRESHOLD) {
// 向下滚动:显示标题,隐藏菜单
nav.classList.add(NAV_STATE_CLASS)
} else {
// 向上滚动:恢复菜单,隐藏标题
nav.classList.remove(NAV_STATE_CLASS)
}

lastScrollTop = scrollTop
ticking = false
}

function onScroll () {
if (!ticking) {
window.requestAnimationFrame(updateNav)
ticking = true
}
}

function init () {
// 设置页面标题文字
const pageName = document.getElementById('page-name')
if (pageName) {
const titleParts = document.title.split(" | June's Blog")
pageName.innerText = titleParts[0] || document.title
}

// 清理旧监听器,避免 PJAX 切换后重复绑定
if (scrollHandler) {
window.removeEventListener('scroll', scrollHandler)
}

// 重置状态
lastScrollTop = window.scrollY || document.documentElement.scrollTop
const nav = document.getElementById('nav')
if (nav) nav.classList.remove(NAV_STATE_CLASS)

scrollHandler = onScroll
window.addEventListener('scroll', scrollHandler, { passive: true })
}

// 全局方法:回到顶部时重置 nav 状态
window.scrollToTop = () => {
const nav = document.getElementById('nav')
if (nav) nav.classList.remove(NAV_STATE_CLASS)
if (typeof btf !== 'undefined') {
btf.scrollToDest(0, 500)
} else {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}

document.addEventListener('DOMContentLoaded', init)
document.addEventListener('pjax:complete', init)
})()

这里用 requestAnimationFrame 控制滚动状态更新,滚动时不会一直直接操作 DOM。PJAX 完成后也会重新初始化标题文字,并清理旧监听器。

如果你的博客标题分隔符不是 | June's Blog,记得把这一行里的站点名改成自己的:

1
const titleParts = document.title.split(" | June's Blog")

3. 添加 nav.css

新建或修改你的自定义 CSS,例如 themes/butterfly/source/css/_custom/layout/nav.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/* 一级菜单居中 */
#nav .menus_items {
display: inline-block !important;
position: absolute !important;
width: fit-content !important;
top: 50%;
left: 50% !important;
transform: translate(-50%, -50%) !important;
transition: opacity .3s, visibility .3s;
}

/* 向下滚动时:隐藏菜单,显示标题 */
#nav.nav-show-title .menus_items {
opacity: 0;
visibility: hidden;
pointer-events: none;
}

#nav.nav-show-title #name-container {
top: 10px;
}

/* 向上滚动时(默认状态):显示菜单,隐藏标题 */
#nav #mask-name-container {
z-index: -1;
pointer-events: none;
}

#nav #name-container {
top: 60px;
display: block;
}

/* 向下滚动时只让标题本体可交互,避免整层遮罩挡住右侧按钮 */
#nav.nav-show-title #mask-name-container {
z-index: 1;
pointer-events: none;
}

#nav.nav-show-title #name-container,
#nav.nav-show-title #page-name {
pointer-events: auto;
}

.menus_item_child li:not(#sidebar-menus li){
float: left;
border-radius: 6px!important;
-webkit-border-radius: 6px!important;
-moz-border-radius: 6px!important;
-ms-border-radius: 6px!important;
-o-border-radius: 6px!important;
}

.menus_item_child:not(#sidebar-menus ul){
left:50%;
translate:-50%;
}

#nav .menus_items .menus_item .menus_item_child li a:hover {
color: white !important;
}

/* 网站标题 hover */
#site-name::before {
opacity: 0;
background-color: var(--june) !important;
border-radius: 8px;
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
-ms-border-radius: 8px;
-o-border-radius: 8px;
transition: .3s;
-webkit-transition: .3s;
-moz-transition: .3s;
-ms-transition: .3s;
-o-transition: .3s;
position: absolute;
top: 0 !important;
right: 0 !important;
z-index: 1 !important;
width: 105%;
height: 120%;
content: "\f015";
box-shadow: 0 0 5px var(--june);
font-family: "Font Awesome 6 Free";
text-align: center;
color: white;
line-height: 34px;
font-size: 18px;
}

#site-name:hover::before {
opacity: 1;
scale: 1.03;
}

/* 确保 site-name 始终在遮罩层之上 */
#blog_name {
pointer-events: none;
}

#site-name {
position: relative;
font-size: 24px;
pointer-events: auto;
z-index: 2;
}

/* 顶栏常驻 */
.nav-fixed #nav {
transform: translateY(58px) !important;
-webkit-transform: translateY(58px) !important;
-moz-transform: translateY(58px) !important;
-ms-transform: translateY(58px) !important;
-o-transform: translateY(58px) !important;
}

#nav {
transition: none !important;
-webkit-transition: none !important;
-moz-transition: none !important;
-ms-transition: none !important;
-o-transition: none !important;
padding: 0 20px;
}

/* 页面标题 hover:显示“回到顶部” */
#page-name::before {
font-size: 18px;
position: absolute;
width: 100%;
height: 100%;
border-radius: 8px;
color: white !important;
top: 0;
left: 0;
content: '回到顶部';
background-color: var(--june);
transition: all .3s;
-webkit-transition: all .3s;
-moz-transition: all .3s;
-ms-transition: all .3s;
-o-transition: all .3s;
opacity: 0;
box-shadow: 0 0 3px var(--june);
line-height: 45px;
}

#mask-name-container {
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
left: 0;
top: 0;
}

#page-name:hover:before {
opacity: 1;
}

@media screen and (max-width: 900px) {
#page-name, #menus {
display: none !important;
}
}

#name-container {
transition: top .3s;
position: absolute;
top: 60px;
left: 50%;
transform: translateX(-50%);
height: 100%;
}

#name-container:hover {
scale: 1.03
}

#page-name {
position: relative;
padding: 10px 30px;
}

#toggleButtons {
display: flex;
flex:1 1 auto;
justify-content: flex-end;
flex-basis: 20px;
}

#toggleButtons .site-page{
margin:3px;
padding: 5px 10px;
white-space: nowrap;
}

/* 首页 nav 隐藏一级菜单右侧小箭头 */
@media screen and (min-width: 768px) {
#menus > .menus_items > .menus_item > .group > i.fa-chevron-down {
display: none;
}
}
注意

mask-name-container 一定要保持 pointer-events: none。它的宽度是整条导航栏,如果让它接收鼠标事件,标题浮上来时就会盖住右侧按钮,导致搜索、开往、随便逛逛这些按钮点不了。

4. 效果说明

滚动页面时:

  • 向下滚动:中间菜单淡出,当前页面标题从下方浮上来;
  • 向上滚动:标题收回,导航菜单恢复;
  • 鼠标放到标题上:标题变成“回到顶部”;
  • 右侧按钮:不受标题遮罩影响,仍然可以正常点击。

常见问题

左上角站点名 hover 没有效果

检查 #site-name 是否有 position: relative 和较高的 z-index,同时 #blog_name 可以设置为 pointer-events: none,再让内部的 #site-name 接收事件。

标题浮上来后右侧按钮无法点击

基本就是 #mask-name-container 挡住了按钮。确保这两段存在:

1
2
3
4
5
6
7
8
9
#nav.nav-show-title #mask-name-container {
z-index: 1;
pointer-events: none;
}

#nav.nav-show-title #name-container,
#nav.nav-show-title #page-name {
pointer-events: auto;
}

点击标题无法回到顶部

确认 nav.pug 中的标题链接是:

1
a(id="page-name" href="javascript:scrollToTop()") PAGE_NAME

同时 nav.js 中需要有:

1
2
3
4
5
6
7
8
9
window.scrollToTop = () => {
const nav = document.getElementById('nav')
if (nav) nav.classList.remove('nav-show-title')
if (typeof btf !== 'undefined') {
btf.scrollToDest(0, 500)
} else {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}