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
}
代码解析
-
"use client":标记这是一个客户端组件
-
useColorMode Hook:
- 使用next-themes的useTheme Hook获取主题状态
- 实现了获取当前主题、设置主题和切换主题的功能
-
ColorModeIcon组件:
- 根据当前主题显示太阳或月亮图标
- 使用了react-icons库中的LuSun和LuMoon图标
-
ColorModeButton组件:
- 使用Chakra UI的IconButton组件
- 点击时触发toggleColorMode函数
- 包含加载状态的骨架屏
-
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组件添加主题切换按钮 - 使用
useColorModeValueHook根据主题返回不同的颜色值 - 创建了一个简单的页面布局来展示主题效果
🧪 第六步:测试和优化
运行开发服务器
bash
npm run dev
在浏览器中打开http://localhost:3000,你应该能看到:
- 一个带有主题切换按钮的页面
- 点击按钮可以在浅色和深色模式之间切换
- 主题状态会自动保存到localStorage
优化建议
- 添加过渡效果:可以为主题切换添加平滑的过渡动画
- 自定义主题:可以扩展Chakra UI的默认主题
- 添加更多主题:除了浅色和深色模式,还可以添加更多主题选项
💡 技术要点解析
next-themes的工作原理
next-themes通过以下方式实现主题管理:
- 在客户端:使用localStorage存储主题偏好
- 在服务器端:检测系统主题并注入相应的CSS类
- 通过React Context API在组件树中共享主题状态
Chakra UI与主题的集成
Chakra UI的主题系统与next-themes完美兼容:
- 提供了丰富的主题变量
- 支持自定义主题
- 组件会自动响应主题变化
客户端组件与服务端渲染
由于主题切换是客户端功能,我们需要使用:
"use client"指令标记客户端组件ClientOnly组件确保在客户端渲染- 骨架屏处理加载状态
🏁 总结
通过本文的教程,我们成功实现了一个优雅的Next.js主题切换功能:
- 初始化了Next.js Page Router项目
- 集成了Chakra UI和next-themes
- 实现了完整的主题切换组件
- 在页面中使用主题功能
这个实现具有以下特点:
- ✅ 支持浅色/深色模式切换
- ✅ 主题状态持久化
- ✅ 响应系统主题偏好
- ✅ 优雅的图标显示
- ✅ 良好的用户体验
希望本文对你有所帮助,让你能够轻松地为自己的Next.js应用添加主题切换功能!
📁 项目地址
本文的完整代码可以在GitHub上找到: github.com/ZhaoDongnan...
如果觉得本文有用,欢迎点赞、收藏、评论和分享!😊