Next.js Page Router + Chakra UI 实现优雅的主题切换 🌓

Next.js Page Router + Chakra UI 实现优雅的主题切换 🌓

从0到1搭建Next.js主题切换功能,让你的应用更具个性化体验

📌 引言

主题切换功能已经成为现代Web应用的标配,它不仅能提升用户体验,还能满足不同用户在不同场景下的使用需求。本文将带你从0到1使用Next.js Page Router和Chakra UI实现一个优雅的主题切换功能。

技术栈

  • Next.js 16 (Page Router)
  • Chakra UI 3
  • next-themes
  • TypeScript

最终效果

  • 🌞 浅色模式和 🌙 深色模式无缝切换
  • 主题状态持久化存储
  • 支持系统主题检测
  • 优雅的图标动画

🚀 第一步:初始化Next.js项目

首先,我们使用官方的create-next-app命令初始化一个Next.js项目:

bash 复制代码
npx create-next-app@latest next-page-router-chakra-theme-switch --typescript --app-router false
cd next-page-router-chakra-theme-switch

注意:我们使用--app-router false来明确使用Page Router,而不是App Router

初始化完成后,项目结构如下:

lua 复制代码
├── src/
│   ├── pages/
│   │   ├── _app.tsx
│   │   ├── _document.tsx
│   │   └── index.tsx
│   └── styles/
├── public/
├── package.json
└── next.config.ts

📦 第二步:安装必要的依赖

接下来,我们需要安装Chakra UI、next-themes和图标库:

bash 复制代码
npm install @chakra-ui/react @emotion/react next-themes react-icons
  • @chakra-ui/react:提供UI组件和主题系统
  • @emotion/react:Chakra UI的依赖,用于样式处理
  • next-themes:Next.js专用的主题管理库
  • react-icons:提供图标支持

⚙️ 第三步:配置Chakra UI和next-themes

配置_app.tsx

修改src/pages/_app.tsx文件,添加Chakra UI和next-themes的提供者:

tsx 复制代码
// src/pages/_app.tsx
import { ChakraProvider, defaultSystem } from "@chakra-ui/react"
import { ThemeProvider } from "next-themes"
import { AppProps } from "next/app"

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ChakraProvider value={defaultSystem}>
      <ThemeProvider attribute="class" disableTransitionOnChange>
        <Component {...pageProps} />
      </ThemeProvider>
    </ChakraProvider>
  )
}

这里我们配置了:

  • ChakraProvider:Chakra UI的根提供者,使用默认主题系统
  • ThemeProvider:next-themes的主题提供者,使用class属性来切换主题
  • disableTransitionOnChange:禁用主题切换时的过渡效果,避免闪烁

🛠️ 第四步:实现主题切换组件

现在,我们创建一个专门的主题切换组件文件src/components/color-mode.tsx

tsx 复制代码
// src/components/color-mode.tsx
"use client"

import type { IconButtonProps } from "@chakra-ui/react"
import { ClientOnly, IconButton, Skeleton } from "@chakra-ui/react"
import { useTheme } from "next-themes"
import * as React from "react"
import { LuMoon, LuSun } from "react-icons/lu"

export type ColorMode = "light" | "dark"

// 自定义Hook:获取和控制主题
export function useColorMode() {
  const { resolvedTheme, setTheme, forcedTheme } = useTheme()
  // 获取当前主题,如果有强制主题则使用强制主题
  const colorMode = forcedTheme || resolvedTheme
  
  // 切换主题的函数
  const toggleColorMode = () => {
    setTheme(resolvedTheme === "dark" ? "light" : "dark")
  }
  
  return {
    colorMode: colorMode as ColorMode,
    setColorMode: setTheme, // 设置主题
    toggleColorMode,       // 切换主题
  }
}

// 根据主题显示不同的图标
export function ColorModeIcon() {
  const { colorMode } = useColorMode()
  // 浅色模式显示太阳图标,深色模式显示月亮图标
  return colorMode === "dark" ? <LuMoon /> : <LuSun /> 
}

// 主题切换按钮组件
export const ColorModeButton = React.forwardRef<
  HTMLButtonElement,
  IconButtonProps
>(function ColorModeButton(props, ref) {
  const { toggleColorMode } = useColorMode()
  
  return (
    <ClientOnly fallback={<Skeleton boxSize="9" />}>
      <IconButton
        onClick={toggleColorMode} // 点击时切换主题
        variant="ghost"           // 使用透明背景的按钮样式
        aria-label="切换颜色模式"
        size="sm"
        ref={ref}
        {...props}
        css={{
          _icon: {
            width: "5",
            height: "5",
          },
        }}
      >
        <ColorModeIcon /> // 显示主题图标
      </IconButton>
    </ClientOnly>
  )
})

// 根据主题返回不同值的Hook
export function useColorModeValue<T>(light: T, dark: T) {
  const { colorMode } = useColorMode()
  return colorMode === "dark" ? dark : light
}

代码解析

  1. "use client":标记这是一个客户端组件

  2. useColorMode Hook

    • 使用next-themes的useTheme Hook获取主题状态
    • 实现了获取当前主题、设置主题和切换主题的功能
  3. ColorModeIcon组件

    • 根据当前主题显示太阳或月亮图标
    • 使用了react-icons库中的LuSun和LuMoon图标
  4. ColorModeButton组件

    • 使用Chakra UI的IconButton组件
    • 点击时触发toggleColorMode函数
    • 包含加载状态的骨架屏
  5. useColorModeValue Hook

    • 一个便捷的Hook,根据当前主题返回不同的值

📄 第五步:在页面中使用主题切换

现在我们在首页src/pages/index.tsx中使用主题切换按钮:

tsx 复制代码
// src/pages/index.tsx
import { ColorModeButton } from "@/components/color-mode"
import { Box, Text, VStack, Heading } from "@chakra-ui/react"
import { useColorModeValue } from "@/components/color-mode"

export default function Page() {
  // 根据主题返回不同的文本颜色
  const textColor = useColorModeValue("gray.800", "gray.200")
  // 根据主题返回不同的背景颜色
  const bgColor = useColorModeValue("white", "gray.900")
  // 根据主题返回不同的强调色
  const accentColor = useColorModeValue("blue.600", "blue.400")
  
  return (
    <Box 
      minHeight="100vh" 
      padding="4rem" 
      backgroundColor={bgColor}
      color={textColor}
    >
      {/* 主题切换按钮 */}
      <Box position="absolute" top="4" right="4">
        <ColorModeButton />
      </Box>
      
      {/* 页面内容 */}
      <VStack spacing="4" alignItems="center" justifyContent="center" minHeight="100vh">
        <Heading as="h1" size="2xl" color={accentColor}>
          Next.js 主题切换示例
        </Heading>
        <Text fontSize="xl">
          当前使用 {useColorModeValue("浅色", "深色")} 模式
        </Text>
        <Text>
          点击右上角的图标切换主题
        </Text>
      </VStack>
    </Box>
  )
}

这里我们:

  • 使用ColorModeButton组件添加主题切换按钮
  • 使用useColorModeValue Hook根据主题返回不同的颜色值
  • 创建了一个简单的页面布局来展示主题效果

🧪 第六步:测试和优化

运行开发服务器

bash 复制代码
npm run dev

在浏览器中打开http://localhost:3000,你应该能看到:

  • 一个带有主题切换按钮的页面
  • 点击按钮可以在浅色和深色模式之间切换
  • 主题状态会自动保存到localStorage

优化建议

  1. 添加过渡效果:可以为主题切换添加平滑的过渡动画
  2. 自定义主题:可以扩展Chakra UI的默认主题
  3. 添加更多主题:除了浅色和深色模式,还可以添加更多主题选项

💡 技术要点解析

next-themes的工作原理

next-themes通过以下方式实现主题管理:

  • 在客户端:使用localStorage存储主题偏好
  • 在服务器端:检测系统主题并注入相应的CSS类
  • 通过React Context API在组件树中共享主题状态

Chakra UI与主题的集成

Chakra UI的主题系统与next-themes完美兼容:

  • 提供了丰富的主题变量
  • 支持自定义主题
  • 组件会自动响应主题变化

客户端组件与服务端渲染

由于主题切换是客户端功能,我们需要使用:

  • "use client"指令标记客户端组件
  • ClientOnly组件确保在客户端渲染
  • 骨架屏处理加载状态

🏁 总结

通过本文的教程,我们成功实现了一个优雅的Next.js主题切换功能:

  1. 初始化了Next.js Page Router项目
  2. 集成了Chakra UI和next-themes
  3. 实现了完整的主题切换组件
  4. 在页面中使用主题功能

这个实现具有以下特点:

  • ✅ 支持浅色/深色模式切换
  • ✅ 主题状态持久化
  • ✅ 响应系统主题偏好
  • ✅ 优雅的图标显示
  • ✅ 良好的用户体验

希望本文对你有所帮助,让你能够轻松地为自己的Next.js应用添加主题切换功能!

📁 项目地址

本文的完整代码可以在GitHub上找到: github.com/ZhaoDongnan...


如果觉得本文有用,欢迎点赞、收藏、评论和分享!😊

相关推荐
FanetheDivine1 天前
Next.js 学习笔记5 使用心得
react.js·next.js
七淮6 天前
Next.js SEO 优化完整方案
前端·next.js
孟祥_成都7 天前
nextjs 16 基础完全指南!(一) - 初步安装
前端·next.js
山依尽10 天前
如何将一个 React SPA 项目迁移到 Next.js 服务端渲染
前端·next.js
人工智能训练12 天前
前端框架选型破局指南:Vue、React、Next.js 从差异到落地全解析
运维·javascript·人工智能·前端框架·vue·react·next.js
米诺zuo20 天前
nextjs文件路由、路由组
前端·next.js
天蓝色的鱼鱼20 天前
Next.js路由全解析:Pages Router 与 App Router,你选对了吗?
前端·next.js
却尘21 天前
一个"New Chat"按钮,为什么要重构整个架构?
前端·javascript·next.js