发布于 2026-01-05 7 阅读
0

在 React 中切换深色模式

在 React 中切换深色模式

当我重建我的作品集网站时,我就知道我想在设计上玩点花样,而深色和浅色模式正合我意。我非常享受这个项目过程中的诸多发现,但我最喜欢的还是可变色的SVG。本教程假设您已熟悉React,我使用的是v17.0.1版本和函数式组件。

首先,我创建了一个基础布局。接下来,我设计了深色浅色配色方案。这需要一些尝试和调整,但在测试了所有组合以确保足够的对比度并尝试了不同的布局后,我发现需要 6 个 CSS 变量。可以说我采用了“深色优先”的开发方法,因为这些变量名在深色主题的上下文中是有意义的。浅色主题的变量较少,但需要用于与背景颜色相同的--button-border场景。--accent

.theme-dark {
  --dark-text: #292929;
  --light-text: #F9F8F8;  
  --dark-background: #2F4550;
  --light-background: #586F7C;
  --accent: #B8DBD9;
  --button-border: #B8DBD9;
}
Enter fullscreen mode Exit fullscreen mode
.theme-light {
  --dark-text: #5E4B56;
  --light-text: #5E4B56;
  --dark-background: #DBE7E4;
  --light-background: #EDDCD2;
  --accent: #DBE7E4;
  --button-border: #5E4B56;
}
Enter fullscreen mode Exit fullscreen mode

然后,我开始为我的基础布局添加颜色:

html, #root {
  background-color: var(--dark-background);
  color: var(--dark-text);
}

nav {
  background-color: var(--dark-background);
  color: var(--light-text);
}

.main-container {
  background-color: var(--light-background);
}
Enter fullscreen mode Exit fullscreen mode

我还设置了我想突出显示的各内容部分的背景--accent--dark-text虽然在深色主题下所有背景都可以,但我将章节标题设置为深色,--light-text使其更加突出。

我找到了 Musthaq Ahamad 的基础主题切换器教程,并着手将其应用到函数式 React 组件中。
我将用于更改主题和检查 localStorage 中主题偏好设置的函数放在一个名为 . 的文件中themes.js

function setTheme(themeName) {
    localStorage.setItem('theme', themeName);
    document.documentElement.className = themeName;
}

function keepTheme() {
  if (localStorage.getItem('theme')) {
    if (localStorage.getItem('theme') === 'theme-dark') {
      setTheme('theme-dark');
    } else if (localStorage.getItem('theme') === 'theme-light') {
      setTheme('theme-light')
    }
  } else {
    setTheme('theme-dark')
  }
}

module.exports = {
  setTheme,
  keepTheme
}
Enter fullscreen mode Exit fullscreen mode

在我的App.js文件中,我添加keepTheme()了我的useEffect()

import { keepTheme } from './utils/themes';

function App() {
  useEffect(() => {
      keepTheme();
  })
}
Enter fullscreen mode Exit fullscreen mode

接下来,我将切换组件添加到导航栏组件中。我参考了Chris Bongers基于Katia De Juan Dribbble 作品的教程,为切换组件设置了样式。然后,我调整了切换组件的大小,并将其默认设置为深色模式。虽然这个切换组件可爱到让人心动,但本教程适用于任何<button>可点击的切换组件<input>。首先,我设置了基本的 JSX 代码、本地状态以及一个用于保存从 localStorage 获取的主题的变量:

import React, { useEffect, useState } from 'react';
import '../styles/toggle.css';
import { setTheme } from '../utils/themes';

function Toggle() {
  const [togClass, setTogClass] = useState('dark');
  let theme = localStorage.getItem('theme');
  return (
        <div className="container--toggle">
           <input type="checkbox" id="toggle" className="toggle--checkbox" onClick={handleOnClick} />
            <label htmlFor="toggle" className="toggle--label">
                <span className="toggle--label-background"></span>
            </label>
        </div>
    )
}

export default Toggle;
Enter fullscreen mode Exit fullscreen mode

当用户点击切换按钮时,我希望页面主题和切换按钮都随之改变。我添加了导入的setTheme()函数,并将setTogClass()本地状态中的值传递给了 handleOnClick 函数。您可以在上面的 JSX 代码中看到它是如何传递给切换按钮的可点击部分的。

const handleOnClick = () => {
  if (localStorage.getItem('theme') === 'theme-dark') {
      setTheme('theme-light');
      setTogClass('light')
  } else {
      setTheme('theme-dark');
      setTogClass('dark')
  }
}
Enter fullscreen mode Exit fullscreen mode

我使用此组件useEffect()来确保本地状态 togClass 始终加载正确的主题。

useEffect(() => {
    if (localStorage.getItem('theme') === 'theme-dark') {
        setTogClass('dark')
    } else if (localStorage.getItem('theme') === 'theme-light') {
        setTogClass('light')
    }
}, [theme])
Enter fullscreen mode Exit fullscreen mode

因为我的切换按钮是一个复选框,所以深色主题应该显示未选中状态(月亮),浅色主题应该显示选中状态(太阳)。但我没能实现defaultChecked预期效果,所以我<input>用这个条件渲染三元运算符(条件运算符)替换了未选中状态:

{
    togClass === "light" ?
    <input type="checkbox" id="toggle" className="toggle--checkbox" onClick={handleOnClick} checked />
    :
    <input type="checkbox" id="toggle" className="toggle--checkbox" onClick={handleOnClick} />
}
Enter fullscreen mode Exit fullscreen mode

如果你使用 `<div>` 标签<button>,你可以像这样轻松地使用条件渲染来更改标签内的 className 属性<button>,并获得相同的效果。

综合所有代码,切换组件的代码如下所示:

import React, { useEffect, useState } from 'react';
import '../styles/toggle.css';
import { setTheme } from '../utils/themes';

function Toggle() {
    const [togClass, setTogClass] = useState('dark');
    let theme = localStorage.getItem('theme');

    const handleOnClick = () => {
        if (localStorage.getItem('theme') === 'theme-dark') {
            setTheme('theme-light');
            setTogClass('light')
        } else {
            setTheme('theme-dark');
            setTogClass('dark')
        }
    }

    useEffect(() => {
        if (localStorage.getItem('theme') === 'theme-dark') {
            setTogClass('dark')
        } else if (localStorage.getItem('theme') === 'theme-light') {
            setTogClass('light')
        }
    }, [theme])

    return (
        <div className="container--toggle">
            {
                togClass === "light" ?
                <input type="checkbox" id="toggle" className="toggle--checkbox" onClick={handleOnClick} checked />
                :
                <input type="checkbox" id="toggle" className="toggle--checkbox" onClick={handleOnClick} />
            }
            <label htmlFor="toggle" className="toggle--label">
                <span className="toggle--label-background"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

更新

要了解我是如何重构此组件的逻辑并使其易于访问的,请阅读React 中的无障碍深色模式切换

最后,我最喜欢的部分:SVG颜色切换!CSS变量在SVG代码中也能用!

我从DEVICON获取了 GitHub 和 Chrome 图标的 SVG 代码。对于 GitHub 图标,我只需要修改一个 `fill` 属性<g>

<g fill="var(--dark-text)">
Enter fullscreen mode Exit fullscreen mode

Chrome 图标在 a<circle>和 a中都有一个 fill 属性<path>

<circle fill="var(--dark-text)" cx="63.624" cy="64.474" r="22.634"></circle><path fill="var(--dark-text)" ...>
Enter fullscreen mode Exit fullscreen mode

结果如下:

桌面版 - 切换按钮看起来像一轮满月,点击后网站会从深色模式切换到浅色模式,此时切换按钮看起来像一轮太阳和几朵云。

移动端 - 切换按钮看起来像一轮满月,点击后网站会从深色模式切换到浅色模式,此时切换按钮看起来像一轮太阳,周围环绕着云朵。

结论

我尽量包含了所有相关的代码,但你也可以在我的GitHub 仓库中查看网站的完整代码。如果你喜欢这篇文章或者有任何疑问,请留言!我也很期待看到你根据本教程创建的作品。

文章来源:https://dev.to/abbeyperini/toggle-dark-mode-in-react-28c9