更新日志

2026

05-22

重构 nav.js:移除 jQuery 依赖和内联样式操作,改用 CSS class(nav-show-title)控制状态切换;正确清理 PJAX 切换时的旧 scroll 监听器;修复默认状态下标题按钮仍可被 hover 的问题(pointer-events: none);修复 rmf.scrollToTop() 未定义的引用

前言

美化之前

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

效果

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

警告

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

参考链接

修改方案

1. 修改nav.pug文件

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

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

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

#toggleButtons
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

如果你的themes下没有Butterfly文件夹(可能是主题安装方式有问题),可以去Butterfly官网下载jerryc127/hexo-theme-butterfly: 🦋 A Hexo Theme: Butterfly (github.com),我下载的版本是4.5.1

小提示

如果你没有定义rmf.scrollToTop()函数,可以将其改为btf.scrollToDest(0, 500)

2. 添加nav.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
document.addEventListener('pjax:complete', tonav);
document.addEventListener('DOMContentLoaded', tonav);
//响应pjax
function tonav(){
document.getElementById("name-container").setAttribute("style", "display:none");

var position = $(window).scrollTop();

$(window).scroll(function () {

var scroll = $(window).scrollTop();

if (scroll > position) {


document.getElementById("name-container").setAttribute("style", "");
document.getElementsByClassName("menus_items")[1].setAttribute("style", "display:none!important");

} else {


document.getElementsByClassName("menus_items")[1].setAttribute("style", "");
document.getElementById("name-container").setAttribute("style", "display:none");

}

position = scroll;

});
function scrollToTop(){
document.getElementsByClassName("menus_items")[1].setAttribute("style","");
document.getElementById("name-container").setAttribute("style","display:none");
btf.scrollToDest(0, 500);
}
//修复没有弄右键菜单的童鞋无法回顶部的问题
document.getElementById("page-name").innerText = document.title.split(" | June's Blog")[0];}

3. 添加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
/*去掉导航栏项目底下的蓝色长条*/
#nav *::after {
background-color: var(--june-theme) !important;
}

.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:calc(-150%)!important;这是估算值,为了保持元素居中的,如果不合适可以自己调
改为:*/
left:50%;
translate:-50%;
}

.menus_item_child:not(#sidebar-menus>.menus_items>.menus_item>.menus_item_child):hover{
border: 1px solid var(--june-theme)!important;
color: white;
/* box-shadow:0 0 3px var(--lyx-theme)!important; */
}

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

/*网站标题部分的增强版*/
#site-name::before {
opacity: 0;
background-color: var(--june-theme) !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;
width: 105%;
height: 120%;
content: "\f015";
box-shadow: 0 0 5px var(--june-theme);
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 {
position: relative;
font-size: 24px; /*一定要把字体调大点,否则效果惨不忍睹!*/
}

/*顶栏常驻*/
.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;
}

/*显示标题*/
#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-theme);
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-theme);
line-height: 45px; /*如果垂直位置不居中可以微调此值,也可以删了*/
}

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

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

#name-container {
transition: all .3s;
-webkit-transition: all .3s;
-moz-transition: all .3s;
-ms-transition: all .3s;
-o-transition: all .3s;
}

#name-container:hover {
scale: 1.03
}

#page-name {
position: relative;
padding: 10px 30px /*如果文字间隔不合理可以微调修改,第二个是水平方向的padding,第一个是垂直的*/
}

#nav {
padding: 0 20px;
}

/* 右侧toggleButtons样式 */
#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;
}

过渡动画

效果

​ 直接滚动鼠标,看导航栏切换即可

参考链接

1. 修改nav.pug文件

修改[blogRoot]\themes\Butterfly\layout\includes\header\nav.pug,在name-container前面加个divmask-name-container

1
2
3
4
5
6
7
8
9
10
11
nav#nav
span#blog_name
a#site-name(href=url_for('/')) #[=config.title]

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

...

2. 修改nav.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
document.addEventListener('pjax:complete', tonav);
document.addEventListener('DOMContentLoaded', tonav);

//响应pjax
function tonav() {
document.getElementById("name-container").setAttribute("style", "display:none");

var position = $(window).scrollTop();

$(window).scroll(function () {

var scroll = $(window).scrollTop();

if (scroll > position) {
document.getElementById("mask-name-container").setAttribute("style", "");
document.getElementById("name-container").setAttribute("style", "top: 10px !important;");
document.getElementsByClassName("menus_items")[1].setAttribute("style", "top: -60px !important;");

} else {
document.getElementById("mask-name-container").setAttribute("style", "z-index: -1 !important");
document.getElementById("name-container").setAttribute("style", "top: 70px !important;");
document.getElementsByClassName("menus_items")[1].setAttribute("style", "");
}

position = scroll;
});

function scrollToTop() {
document.getElementsByClassName("menus_items")[1].setAttribute("style", "");
document.getElementById("name-container").setAttribute("style", "display:none");
btf.scrollToDest(0, 500);
}

//修复没有弄右键菜单的童鞋无法回顶部的问题
document.getElementById("page-name").innerText = document.title.split(" | June's Blog")[0];
}

3. 修改nav.css

主要就是修改了菜单栏menus_items、文章名的容器name-container和遮罩mask-name-container

1
2
3
4
5
6
7
8
9
#nav .menus_items {
display: inline-block !important;
transition: all .3s;
position: absolute !important;
width: fit-content !important;
top: 50%;
left: 50% !important;
transform: translate(-50%, -50%) !important;
}
1
2
3
4
5
6
7
8
9
10
11
12
#name-container {
transition: all .3s;
-webkit-transition: all .3s;
-moz-transition: all .3s;
-ms-transition: all .3s;
-o-transition: all .3s;
position: absolute;
top: 60px;
left: 50%;
transform: translateX(-50%);
height: 100%;
}
1
2
3
4
5
6
7
8
#mask-name-container {
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
left: 0;
top: 0;
}

如果你的站点名(左上角)hover没效果的话,再加上

1
2
3
#site-name::before {
z-index: 1 !important;
}
完整的导航栏样式
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
/* 一级菜单居中 */
#nav .menus_items {
display: inline-block !important;
transition: all .3s;
position: absolute !important;
width: fit-content !important;
top: 50%;
left: 50% !important;
transform: translate(-50%, -50%) !important;
}

.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:calc(-150%)!important;这是估算值,为了保持元素居中的,如果不合适可以自己调
改为:*/
left:50%;
translate:-50%;
}

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

/*网站标题部分的增强版*/
#site-name::before {
opacity: 0;
background-color: var(--june-theme) !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-theme);
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 始终在遮罩层之上,hover 不受 mask-name-container 遮挡 */
#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;
}

/*显示标题*/
#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-theme);
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-theme);
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: all .3s;
-webkit-transition: all .3s;
-moz-transition: all .3s;
-ms-transition: all .3s;
-o-transition: all .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 /*如果文字间隔不合理可以微调修改,第二个是水平方向的padding,第一个是垂直的*/
}

#nav {
padding: 0 20px;
}

#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;
}

重构:CSS Class 驱动

重构说明

上面的方案虽然能用,但实现比较粗糙:依赖 jQuery、用内联 style.cssText 覆盖样式、PJAX 切换时重复绑定 scroll 事件不清理。这里记录重构后的方案,核心思路是 JS 只管切换 class,CSS 负责所有视觉表现

1. 修改 nav.pug

移除内联 stylehref 改为全局暴露的 scrollToTop() 方法

1
2
3
4
5
6
7
8
9
10
11
12
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
...

2. 重写 nav.js

不再操作 style.cssText,只在 #nav 上切换 nav-show-title class;正确清理旧监听器避免 PJAX 重复绑定

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
/**
* 导航栏滚动切换:向下滚动显示页面标题,向上滚动恢复菜单
* 通过 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
}

// 清理旧监听器
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 })
}

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)
})()

3. 修改 nav.css(状态切换部分)

.nav-show-title class 控制菜单/标题的显隐,pointer-events: none 防止隐藏的标题按钮被 hover 到

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
/* 一级菜单居中 */
#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: auto;
}

/* name-container 基础样式 */
#mask-name-container {
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
left: 0;
top: 0;
}

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

#name-container:hover {
scale: 1.03;
}