自定义导航栏
定制 VitePress 导航栏的外观和功能。
方式一:CSS 变量覆盖
修改导航栏颜色
css
/* .vitepress/theme/style.css */
:root {
--vp-nav-height: 64px;
--vp-nav-bg-color: var(--vp-c-bg);
--vp-nav-logo-height: 32px;
}
/* 深色模式 */
.dark {
--vp-nav-bg-color: rgba(17, 17, 17, 0.8);
}
/* 半透明导航栏 */
.VPNav {
background: transparent !important;
backdrop-filter: blur(12px);
}
/* 固定导航栏 */
.VPNav {
position: fixed !important;
width: 100%;
z-index: 100;
}
/* 调整内容区域顶部间距 */
.VPContent {
padding-top: var(--vp-nav-height) !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
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
修改导航项样式
css
/* 导航链接样式 */
.VPNavBarMenuLink {
font-weight: 500;
letter-spacing: 0.02em;
}
/* 激活状态 */
.VPNavBarMenuLink.active {
color: var(--vp-c-brand-1);
border-bottom: 2px solid var(--vp-c-brand-1);
}
/* 悬停效果 */
.VPNavBarMenuLink:hover {
color: var(--vp-c-brand-1);
transition: color 0.25s;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
方式二:使用插槽
添加搜索框
vue
<!-- .vitepress/theme/index.ts -->
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import SearchBox from './components/SearchBox.vue'
export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
'nav-bar-content-after': () => h(SearchBox)
})
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
vue
<!-- .vitepress/theme/components/SearchBox.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vitepress'
const query = ref('')
const router = useRouter()
const handleSearch = () => {
if (query.value.trim()) {
router.go(`/search?q=${encodeURIComponent(query.value)}`)
}
}
</script>
<template>
<div class="search-box">
<input
v-model="query"
type="text"
placeholder="搜索..."
@keyup.enter="handleSearch"
/>
<button @click="handleSearch">
<svg>...</svg>
</button>
</div>
</template>
<style scoped>
.search-box {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.25rem 0.75rem;
background: var(--vp-c-bg-alt);
border-radius: 8px;
border: 1px solid var(--vp-c-divider);
}
.search-box input {
background: transparent;
border: none;
outline: none;
padding: 0.25rem;
font-size: 0.875rem;
color: var(--vp-c-text-1);
}
.search-box button {
background: transparent;
border: none;
cursor: pointer;
color: var(--vp-c-text-2);
}
</style>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
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
添加主题切换按钮
vue
<!-- .vitepress/theme/components/ThemeSwitch.vue -->
<script setup lang="ts">
import { useData } from 'vitepress'
const { isDark, toggleDark } = useData()
</script>
<template>
<button class="theme-switch" @click="toggleDark()" :title="isDark ? '切换到亮色模式' : '切换到暗色模式'">
<svg v-if="isDark" class="sun" viewBox="0 0 24 24" width="20" height="20">
<path fill="currentColor" d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.5 3.5l1.41 1.41L2.83 7 1.5 5.5 3.5 3.5zM18 18l1.41-1.41-2.12-2.12L16 15.5l2 2.5zM1 11v2h3v-2H1zm19 0v2h3v-2h-3zM3.5 20.5l1.41-1.41-2.12-2.12L1.5 18.5l2 2zm14.5-14l1.41 1.41L21.17 7 20 5.5 18 6.5z"/>
</svg>
<svg v-else class="moon" viewBox="0 0 24 24" width="20" height="20">
<path fill="currentColor" d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.5-4.5 10-10 10S2 17.6 2 12.1 6.5 2 12 2h.1a7 7 0 0 0-.1 14zm-6.5-5a5.5 5.5 0 1 0 11 0 5.5 5.5 0 0 0-11 0z"/>
</svg>
</button>
</template>
<style scoped>
.theme-switch {
background: transparent;
border: none;
cursor: pointer;
padding: 0.5rem;
border-radius: 8px;
color: var(--vp-c-text-2);
transition: all 0.2s;
}
.theme-switch:hover {
color: var(--vp-c-text-1);
background: var(--vp-c-bg-alt);
}
</style>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
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
方式三:完全自定义导航
vue
<!-- .vitepress/theme/components/CustomNav.vue -->
<script setup lang="ts">
import { useData, useRouter } from 'vitepress'
import { ref } from 'vue'
const { site, page } = useData()
const router = useRouter()
const isMenuOpen = ref(false)
const nav = [
{ text: '首页', link: '/' },
{ text: '指南', link: '/guide/' },
{ text: 'API', link: '/reference/' },
{ text: '博客', link: '/blog/' }
]
const isActive = (link: string) => {
return page.value.relativePath.startsWith(link.replace('/', ''))
}
</script>
<template>
<header class="custom-nav">
<div class="container">
<a href="/" class="logo">
<img src="/logo.svg" alt="Logo" />
<span>{{ site.title }}</span>
</a>
<nav class="menu" :class="{ open: isMenuOpen }">
<a
v-for="item in nav"
:key="item.link"
:href="item.link"
:class="{ active: isActive(item.link) }"
>
{{ item.text }}
</a>
</nav>
<button class="menu-toggle" @click="isMenuOpen = !isMenuOpen">
<span></span>
<span></span>
<span></span>
</button>
</div>
</header>
</template>
<style scoped>
.custom-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 64px;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
z-index: 100;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
font-weight: 600;
font-size: 1.1rem;
text-decoration: none;
color: var(--vp-c-text-1);
}
.logo img {
height: 32px;
}
.menu {
display: flex;
gap: 32px;
}
.menu a {
text-decoration: none;
color: var(--vp-c-text-2);
font-weight: 500;
transition: color 0.2s;
}
.menu a:hover,
.menu a.active {
color: var(--vp-c-brand-1);
}
.menu-toggle {
display: none;
flex-direction: column;
gap: 5px;
background: none;
border: none;
cursor: pointer;
padding: 8px;
}
.menu-toggle span {
width: 24px;
height: 2px;
background: var(--vp-c-text-1);
transition: all 0.3s;
}
@media (max-width: 768px) {
.menu {
display: none;
position: absolute;
top: 64px;
left: 0;
right: 0;
background: var(--vp-c-bg);
flex-direction: column;
padding: 16px;
border-bottom: 1px solid var(--vp-c-divider);
}
.menu.open {
display: flex;
}
.menu-toggle {
display: flex;
}
}
</style>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
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