在Nextjs项目中实现深色模式时,使用CSS变量可以统一管理不同主题的样式值,减少重复代码,同时让主题切换的逻辑更清晰。接下来我们通过具体步骤实现完整的深色模式功能。

什么是CSS变量
CSS变量也就是自定义属性,以--开头定义,通过var()函数调用,它可以继承也可以被覆盖,非常适合用来存储主题相关的样式值。比如我们可以把背景色、文字颜色等样式定义为CSS变量,切换主题时只需要修改根元素上的变量值即可。
在Nextjs中定义CSS变量
首先在Nextjs的全局样式文件中定义两组主题的CSS变量,分别对应浅色模式和深色模式。我们可以在styles/globals.css中添加如下代码:
/* 定义浅色模式变量 */
:root {
--bg-color: #ffffff;
--text-color: #333333;
--border-color: #e5e5e5;
--primary-color: #1677ff;
}
/* 定义深色模式变量,通过data-theme属性区分 */
[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #f0f0f0;
--border-color: #333333;
--primary-color: #4096ff;
}
这里我们通过data-theme属性来区分不同的主题,当根元素上设置data-theme="dark"时,就会应用深色模式的变量值。
实现主题切换逻辑
接下来我们需要实现主题切换的交互功能,同时需要处理主题的持久化和系统偏好检测。首先在pages/_app.js中添加状态管理和初始化逻辑:
import { useEffect, useState } from 'react';
import '../styles/globals.css';
function MyApp({ Component, pageProps }) {
const [theme, setTheme] = useState('light');
// 初始化主题:优先读取本地存储,其次检测系统偏好
useEffect(() => {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
setTheme(savedTheme);
} else {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setTheme(prefersDark ? 'dark' : 'light');
}
}, []);
// 主题变化时更新DOM和本地存储
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}, [theme]);
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<div style={{ backgroundColor: 'var(--bg-color)', color: 'var(--text-color)', minHeight: '100vh', padding: '20px' }}>
<button onClick={toggleTheme} style={{ backgroundColor: 'var(--primary-color)', color: '#fff', border: 'none', padding: '8px 16px', borderRadius: '4px' }}>
{theme === 'light' ? '切换深色模式' : '切换浅色模式'}
</button>
<Component {...pageProps} />
</div>
);
}
export default MyApp;
上面的代码中,我们通过useState管理当前主题状态,初始化时优先读取本地存储的主题,若没有则检测系统的prefers-color-scheme媒体查询判断用户偏好。主题变化时,同时修改根元素的data-theme属性和本地存储的值。
在组件中使用CSS变量
定义好变量和切换逻辑后,所有组件的样式都可以直接使用这些CSS变量,无需关心当前是什么主题。比如一个简单的卡片组件:
export default function Card() {
return (
<div style={{
backgroundColor: 'var(--bg-color)',
color: 'var(--text-color)',
border: '1px solid var(--border-color)',
borderRadius: '8px',
padding: '16px',
marginTop: '20px'
}}>
<h3 style={{ color: 'var(--primary-color)' }}>卡片标题</h3>
<p>这是一段卡片内容,样式会自动跟随主题切换变化。</p>
</div>
);
}
避免样式闪烁的优化
在页面加载时,可能会出现短暂的白屏或者样式闪烁问题,这是因为JavaScript执行前,页面先渲染了默认的浅色样式。我们可以在pages/_document.js中添加内联脚本,在页面渲染前就读取本地存储的主题并设置根元素的属性:
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
render() {
return (
<Html>
<Head />
<body>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
var theme = localStorage.getItem('theme');
if (!theme) {
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
theme = prefersDark ? 'dark' : 'light';
}
document.documentElement.setAttribute('data-theme', theme);
})();
`
}}
/>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
这段脚本会在页面解析时就执行,提前设置好data-theme属性,避免样式闪烁的问题。
常见问题说明
- 如果需要支持更多主题,只需要添加对应的
data-theme选择器和对应的CSS变量即可,扩展性很强 - CSS变量的作用域遵循CSS的继承规则,如果在某个组件内部重新定义同名变量,只会覆盖该组件及其子组件的变量值
- 如果项目使用了CSS Modules,也可以在模块样式中使用
var(--变量名)的方式调用全局定义的CSS变量