Skip to content

自定义导航栏

定制 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;
}

修改导航项样式

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

方式二:使用插槽

添加搜索框

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

添加主题切换按钮

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>

方式三:完全自定义导航

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>

参考链接

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献