导航栏是几乎所有网站都必备的组件,一个好的响应式导航栏需要在不同设备上都能提供出色的用户体验。本节将介绍如何使用 Tailwind CSS 实现功能完善的响应式导航栏。

基础导航栏结构

桌面端导航

  1. <nav class="bg-white shadow">
  2. <div class="max-w-7xl mx-auto px-4">
  3. <div class="flex justify-between h-16">
  4. <!-- Logo -->
  5. <div class="flex-shrink-0 flex items-center">
  6. <img class="h-8 w-auto" src="/logo.svg" alt="Logo" />
  7. </div>
  8. <!-- 主导航菜单 -->
  9. <div class="hidden md:flex items-center space-x-8">
  10. <a href="/" class="text-gray-900 hover:text-gray-600 px-3 py-2 text-sm font-medium">
  11. 首页
  12. </a>
  13. <a href="/products" class="text-gray-900 hover:text-gray-600 px-3 py-2 text-sm font-medium">
  14. 产品
  15. </a>
  16. <a href="/about" class="text-gray-900 hover:text-gray-600 px-3 py-2 text-sm font-medium">
  17. 关于
  18. </a>
  19. <a href="/contact" class="text-gray-900 hover:text-gray-600 px-3 py-2 text-sm font-medium">
  20. 联系我们
  21. </a>
  22. </div>
  23. <!-- 用户菜单 -->
  24. <div class="hidden md:flex items-center space-x-4">
  25. <button class="bg-gray-100 hover:bg-gray-200 px-4 py-2 rounded-lg text-sm font-medium">
  26. 登录
  27. </button>
  28. <button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium">
  29. 注册
  30. </button>
  31. </div>
  32. </div>
  33. </div>
  34. </nav>

移动端菜单按钮

  1. <!-- 汉堡菜单按钮 -->
  2. <div class="flex md:hidden">
  3. <button type="button"
  4. class="inline-flex items-center justify-center p-2 rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-100"
  5. aria-controls="mobile-menu"
  6. aria-expanded="false">
  7. <span class="sr-only">打开主菜单</span>
  8. <!-- 汉堡菜单图标 -->
  9. <svg class="block h-6 w-6"
  10. fill="none"
  11. viewBox="0 0 24 24"
  12. stroke="currentColor">
  13. <path stroke-linecap="round"
  14. stroke-linejoin="round"
  15. stroke-width="2"
  16. d="M4 6h16M4 12h16M4 18h16" />
  17. </svg>
  18. <!-- 关闭图标 -->
  19. <svg class="hidden h-6 w-6"
  20. fill="none"
  21. viewBox="0 0 24 24"
  22. stroke="currentColor">
  23. <path stroke-linecap="round"
  24. stroke-linejoin="round"
  25. stroke-width="2"
  26. d="M6 18L18 6M6 6l12 12" />
  27. </svg>
  28. </button>
  29. </div>

移动端菜单面板

  1. <!-- 移动端菜单 -->
  2. <div class="md:hidden" id="mobile-menu">
  3. <div class="px-2 pt-2 pb-3 space-y-1">
  4. <a href="/" class="block px-3 py-2 rounded-md text-base font-medium text-gray-900 hover:bg-gray-100">
  5. 首页
  6. </a>
  7. <a href="/products" class="block px-3 py-2 rounded-md text-base font-medium text-gray-900 hover:bg-gray-100">
  8. 产品
  9. </a>
  10. <a href="/about" class="block px-3 py-2 rounded-md text-base font-medium text-gray-900 hover:bg-gray-100">
  11. 关于
  12. </a>
  13. <a href="/contact" class="block px-3 py-2 rounded-md text-base font-medium text-gray-900 hover:bg-gray-100">
  14. 联系我们
  15. </a>
  16. </div>
  17. <div class="pt-4 pb-3 border-t border-gray-200">
  18. <div class="px-2 space-y-1">
  19. <button class="block w-full text-left px-3 py-2 rounded-md text-base font-medium text-gray-900 hover:bg-gray-100">
  20. 登录
  21. </button>
  22. <button class="block w-full text-left px-3 py-2 rounded-md text-base font-medium bg-blue-500 text-white hover:bg-blue-600">
  23. 注册
  24. </button>
  25. </div>
  26. </div>
  27. </div>

交互功能实现

菜单切换功能

  1. function setupMobileMenu() {
  2. const button = document.querySelector('[aria-controls="mobile-menu"]')
  3. const menu = document.getElementById('mobile-menu')
  4. const icons = button.querySelectorAll('svg')
  5. button.addEventListener('click', () => {
  6. const expanded = button.getAttribute('aria-expanded') === 'true'
  7. button.setAttribute('aria-expanded', !expanded)
  8. menu.classList.toggle('hidden')
  9. icons[0].classList.toggle('hidden')
  10. icons[1].classList.toggle('hidden')
  11. })
  12. }

滚动处理

  1. function handleNavbarScroll() {
  2. const navbar = document.querySelector('nav')
  3. let lastScroll = 0
  4. window.addEventListener('scroll', () => {
  5. const currentScroll = window.pageYOffset
  6. if (currentScroll <= 0) {
  7. navbar.classList.remove('-translate-y-full')
  8. return
  9. }
  10. if (currentScroll > lastScroll && !navbar.contains(document.activeElement)) {
  11. // 向下滚动时隐藏
  12. navbar.classList.add('-translate-y-full')
  13. } else {
  14. // 向上滚动时显示
  15. navbar.classList.remove('-translate-y-full')
  16. }
  17. lastScroll = currentScroll
  18. })
  19. }

高级功能实现

下拉菜单

  1. <div class="relative group">
  2. <button class="flex items-center space-x-1 text-gray-900 hover:text-gray-600 px-3 py-2 text-sm font-medium">
  3. <span>产品</span>
  4. <svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
  5. <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
  6. </svg>
  7. </button>
  8. <div class="absolute left-0 w-48 mt-2 bg-white rounded-lg shadow-lg py-2 hidden group-hover:block">
  9. <a href="/products/category1" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
  10. 分类一
  11. </a>
  12. <a href="/products/category2" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
  13. 分类二
  14. </a>
  15. <a href="/products/category3" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
  16. 分类三
  17. </a>
  18. </div>
  19. </div>

搜索框集成

  1. <div class="flex-1 flex justify-center px-2 lg:ml-6 lg:justify-end">
  2. <div class="max-w-lg w-full lg:max-w-xs">
  3. <label for="search" class="sr-only">搜索</label>
  4. <div class="relative">
  5. <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
  6. <svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  7. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
  8. </svg>
  9. </div>
  10. <input id="search"
  11. type="search"
  12. class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
  13. placeholder="搜索" />
  14. </div>
  15. </div>
  16. </div>

样式优化

活跃状态

  1. <a href="/products"
  2. class="text-gray-900 hover:text-gray-600 px-3 py-2 text-sm font-medium
  3. border-b-2 border-transparent hover:border-gray-300
  4. transition-colors duration-200
  5. aria-[current=page]:text-blue-600 aria-[current=page]:border-blue-600"
  6. aria-current="page">
  7. 产品
  8. </a>

滚动动效

  1. <nav class="fixed top-0 inset-x-0 bg-white shadow
  2. transform transition-transform duration-300
  3. motion-reduce:transition-none">
  4. <!-- 导航内容 -->
  5. </nav>

深色模式支持

  1. <nav class="bg-white dark:bg-gray-800 shadow">
  2. <div class="max-w-7xl mx-auto px-4">
  3. <div class="flex justify-between h-16">
  4. <!-- Logo -->
  5. <div class="flex-shrink-0 flex items-center">
  6. <img class="h-8 w-auto block dark:hidden" src="/logo-light.svg" alt="Logo" />
  7. <img class="h-8 w-auto hidden dark:block" src="/logo-dark.svg" alt="Logo" />
  8. </div>
  9. <!-- 导航链接 -->
  10. <div class="hidden md:flex items-center space-x-8">
  11. <a href="/" class="text-gray-900 dark:text-gray-100 hover:text-gray-600 dark:hover:text-gray-300">
  12. 首页
  13. </a>
  14. <!-- 更多链接 -->
  15. </div>
  16. </div>
  17. </div>
  18. </nav>

性能优化

延迟加载

  1. // 延迟加载非关键资源
  2. function lazyLoadNavResources() {
  3. const images = document.querySelectorAll('nav img[data-src]')
  4. const observer = new IntersectionObserver((entries) => {
  5. entries.forEach(entry => {
  6. if (entry.isIntersecting) {
  7. const img = entry.target
  8. img.src = img.dataset.src
  9. observer.unobserve(img)
  10. }
  11. })
  12. })
  13. images.forEach(img => observer.observe(img))
  14. }

动画性能

  1. /* 使用 transform 而不是 margin/top 来实现动画 */
  2. .nav-animation {
  3. transform: translateY(0);
  4. transition: transform 0.3s ease;
  5. will-change: transform;
  6. }
  7. .nav-hidden {
  8. transform: translateY(-100%);
  9. }

可访问性增强

键盘导航

  1. function setupKeyboardNav() {
  2. const menuItems = document.querySelectorAll('nav a, nav button')
  3. menuItems.forEach((item, index) => {
  4. item.addEventListener('keydown', (e) => {
  5. switch (e.key) {
  6. case 'ArrowRight':
  7. e.preventDefault()
  8. menuItems[(index + 1) % menuItems.length].focus()
  9. break
  10. case 'ArrowLeft':
  11. e.preventDefault()
  12. menuItems[(index - 1 + menuItems.length) % menuItems.length].focus()
  13. break
  14. }
  15. })
  16. })
  17. }

ARIA 属性支持

  1. <nav role="navigation" aria-label="主导航">
  2. <button type="button"
  3. aria-controls="mobile-menu"
  4. aria-expanded="false"
  5. aria-label="打开主菜单">
  6. <!-- 按钮内容 -->
  7. </button>
  8. <div id="mobile-menu"
  9. role="menu"
  10. aria-orientation="vertical"
  11. aria-labelledby="menu-button">
  12. <!-- 菜单内容 -->
  13. </div>
  14. </nav>

最佳实践

  1. 响应式设计原则

    • 移动优先设计
    • 断点设置合理
    • 内容优先级排序
  2. 交互设计要点

    • 清晰的视觉反馈
    • 平滑的动画过渡
    • 直观的操作方式
  3. 性能优化策略

    • 合理使用 CSS transforms
    • 避免布局抖动
    • 资源按需加载
  4. 可访问性建议

    • 完善的键盘支持
    • 适当的 ARIA 属性
    • 合理的颜色对比度