更新日志
完善导航栏教程:将后续使用的 nav-show-title 写法并入正文教程;补充遮罩层 pointer-events 处理,修复标题浮上来时右侧“开往”“随便逛逛”和搜索按钮无法点击的问题
整理导航栏写法:将滚动切换逻辑调整为 nav-show-title 状态控制,补充 PJAX 场景下的 scroll 监听清理;补充无 rmf.scrollToTop() 时的回顶部写法
前言
美化之前
美化的时候需要修改网站的源文件、添加样式、js之类的,需要一点基础,可以参考
🏠 站内链接,放心访问
Hexo博客添加自定义css和js文件
June's Blog
🏠 站内链接,放心访问
Butterfly自用全局变量
June's Blog
效果
修改效果:导航栏居中,二级菜单横向展示,导航栏显示网站、文章的标题等

参考链接
🔗 站外链接,请注意甄别
关于Butterfly的导航栏的一些教程
Ariasakaの小窝
🔗 站外链接,请注意甄别
butterfly导航栏修改方案(自用方案)
安知鱼
修改方案
这一版主要实现几个效果:一级菜单居中、二级菜单横向展开、向下滚动时显示当前页面标题、点击标题回到顶部,同时保留右侧的“开往”“随便逛逛”和搜索按钮。
核心思路很简单:nav.js 只负责根据滚动方向给 #nav 添加或移除 nav-show-title,具体的显示、隐藏和过渡动画都交给 CSS。
1. 修改 nav.pug 文件
修改 [blogRoot]\themes\Butterfly\layout\includes\header\nav.pug。
这里在菜单区域里加入 mask-name-container 和 name-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 的引入,可以先看这篇:
🏠 站内链接,放心访问
Hexo博客添加自定义css和js文件
June's Blog
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
|
;(() => { 'use strict'
const NAV_STATE_CLASS = 'nav-show-title' 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) })()
|
这里用 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; }
#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; }
#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; }
#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; }
@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' }) } }
|