如何在 React 中创建暗黑模式组件
本教程的所有代码都可以在此存储库中找到,并且本教程的视频版本如下所示。
目录
为 Web 应用提供暗黑模式已成为用户的期望,实现它的方法有很多。通常,最有效的方法是利用CSS 变量的强大功能。
在本教程中,我们将展示如何将整个暗模式功能捆绑到一个<DarkMode />
组件中,您可以随身携带并放置在任何应用程序中。
这个组件不仅会在页面关闭或刷新后保留你选择的设置,还会尊重用户prefers-color-scheme
在浏览器中的设置。太酷了!
让我们深入研究一下。
现场演示
在开始之前,我们先来看一下最终产品的演示,以便您了解本教程的预期内容。完成后,您将拥有自己的<DarkMode />
组件,可以将其放入任何应用程序中以实现此功能。
先决条件
我假设您对 React有基本的了解。
您无需成为专家。实际上,我们没有单个状态变量,也没有任何钩子或生命周期方法。我们的目标(也应该始终如此)是尽量降低复杂性。此功能不需要它们。
我们将在本教程中使用Create React App,因为它是一种极其简单的方法,可以快速轻松地建立可在其上构建的 React 应用程序模板。
即使您选择不使用 CRA,您仍然可以继续学习本教程。我们将编写纯 CSS,但为了准确复制示例,您需要安装webpack并配置 CSS 加载器来支持import
CSS 文件的语法。
如果您不使用,您可以简单地在您的 CSS 文件中webpack
使用一个元素,而不是导入它们。<link>
index.html
我们还将使用Typescript,这是我最近构建的每个 Web 项目的默认语言。即使你对 Typescript 不是很熟悉,也应该能够跟上,因为这些示例中显式输入的数量很少。
最后,我添加了一个部分,介绍如何使用React 测试库为组件添加测试。此部分是可选的。
初始化项目
如果您正在使用 CRA,则运行以下命令(如果您有自己的现有项目,则请忽略)
npx create-react-app dark-mode-example --template typescript
添加样式
当应用程序加载时,它将按照以下优先级顺序确定暗/亮设置:
- 用户之前的切换设置
- 用户的浏览器偏好设置
- 灯光模式
我们将首先创建处理暗模式的 CSS。
src/DarkMode.css
/* 1 */
:root {
--font-color: #333;
--background-color: #eee;
--link-color: cornflowerblue;
}
/* 2 */
[data-theme="dark"] {
--font-color: #eee;
--background-color: #333;
--link-color: lightblue;
}
/* 3 */
body {
background-color: var(--background-color);
color: var(--font-color);
}
a {
color: var(--link-color);
}
-
选择
:root
器与代表 DOM 树的根元素匹配。您在此处放置的任何内容都将在应用程序的任何位置可用。在这里,我们将创建用于保存浅色主题颜色的CSS 变量。 -
这里我们设置了主题的颜色
dark
。使用属性选择器,我们可以定位任何带有data-theme="dark"
属性的元素。这是一个自定义属性,我们将自己将其添加到<html>
元素上。 -
--background-color
我们设置应用程序的背景颜色和文本颜色。这将始终是和变量的值--font-color
。由于级联,这些变量的值将根据属性的设置时间而变化。data-theme="dark"
深色值在根值之后设置,因此如果选择器应用,这些变量的初始(浅色)值将被深色值覆盖。
注意,我还在这里添加了自定义链接颜色,它会根据主题的值而变化。您可以在此处添加任意数量的自定义颜色,并将它们全部通过亮/暗切换按钮控制。您可以尝试自己添加更多颜色!
添加切换按钮
接下来,我们将根据此示例创建一个自定义复选框输入,使其看起来像一个切换开关。
我不会评论这段 CSS 是如何工作的,因为它不在本教程的讨论范围内,而且与暗/亮模式无关。下面的样式只是为了覆盖默认 HTML 复选框的外观。
将它们添加到上述代码下方src/DarkMode.css
src/DarkMode.css
/* Custom Dark Mode Toggle Element */
.toggle-theme-wrapper {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
}
.toggle-theme-wrapper span {
font-size: 28px;
}
.toggle-theme {
position: relative;
display: inline-block;
height: 34px;
width: 60px;
}
.toggle-theme input {
display: none;
}
.slider {
background-color: #ccc;
position: absolute;
cursor: pointer;
bottom: 0;
left: 0;
right: 0;
top: 0;
transition: 0.2s;
}
.slider:before {
background-color: #fff;
bottom: 4px;
content: "";
height: 26px;
left: 4px;
position: absolute;
transition: 0.4s;
width: 26px;
}
input:checked + .slider:before {
transform: translateX(26px);
}
input:checked + .slider {
background-color: cornflowerblue;
}
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
创建 DarkMode 组件
现在我们将创建我们的DarkMode
组件。
首先我们只关注组件本身的结构,不关注事件或功能:
src/DarkMode.tsx
import "./DarkMode.css";
const DarkMode = () => {
return (
<div className="toggle-theme-wrapper">
<span>☀️</span>
<label className="toggle-theme" htmlFor="checkbox">
<input
type="checkbox"
id="checkbox"
/>
<div className="slider round"></div>
</label>
<span>🌒</span>
</div>
);
};
export default DarkMode;
该<input>
元素将处理颜色主题的状态。当颜色主题被选中时checked
,暗色模式处于活动状态;当未选中时,亮色模式处于活动状态。
如果您渲染此组件,您应该有一个美观的自定义切换按钮,但没有任何功能。
为了使我们的切换开关工作,我们必须攻击一些 Javascript 函数来处理onChange
在切换复选框时触发的输入事件。
我们还需要决定页面或应用程序首次加载时默认显示的模式。这里有很多内容需要解释;示例下方带编号的注释中会解释具体发生了什么。
src/DarkMode.tsx
import "./DarkMode.css";
import { ChangeEventHandler } from "react";
// 1
const setDark = () => {
// 2
localStorage.setItem("theme", "dark");
// 3
document.documentElement.setAttribute("data-theme", "dark");
};
const setLight = () => {
localStorage.setItem("theme", "light");
document.documentElement.setAttribute("data-theme", "light");
};
// 4
const storedTheme = localStorage.getItem("theme");
const prefersDark =
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
const defaultDark =
storedTheme === "dark" || (storedTheme === null && prefersDark);
if (defaultDark) {
setDark();
}
// 5
const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
if (e.target.checked) {
setDark();
} else {
setLight();
}
};
const DarkMode = () => {
return (
<div className="toggle-theme-wrapper">
<span>☀️</span>
<label className="toggle-theme" htmlFor="checkbox">
<input
type="checkbox"
id="checkbox"
// 6
onChange={toggleTheme}
defaultChecked={defaultDark}
/>
<div className="slider round"></div>
</label>
<span>🌒</span>
</div>
);
};
export default DarkMode;
-
setDark
我们创建了名为和 的函数setLight
,它们的作用正如名称所描述的那样。我们希望这些函数尽可能简单。当调用它们时,我们希望应用能够切换到亮模式或暗模式。 -
这就是我们处理持久化的方式。使用localStorage可以让我们保存一个值,即使用户关闭应用或重新加载页面后,该值仍然会保留。每次设置亮色或暗色模式时,我们都将该值保存在
theme
的属性中localStorage
。 -
这里我们设置DOM 元素
data-theme="dark"
的 (或 light) 值<html>
。这实际上会更新我们应用中的颜色。添加该属性后,[data-theme="dark"]
CSS 中的选择器将变为活动状态,并设置深色变量(反之亦然)。 -
注释 4 下的部分是在页面加载完成,实际切换开关尚未使用时建立“初始”状态。如果存在,
storedTheme
则获取其值。检查媒体查询中用户浏览器设置的prefers-color-scheme。最后,会检查这两项,并根据我们在本教程开头建立的 3 条优先级规则,决定是否默认使用暗黑模式。如果结果为 true,我们会在组件渲染之前将应用设置为暗黑模式。(注意,我们之所以能这样做,是因为我们定位的是已经存在的属性。)localStorage
prefersDark
defaultDark
<html>
-
这是我们编写的事件处理函数,用于捕获用户点击复选框时发生的更改事件。如果复选框处于选中状态,
checked
则启用暗色模式;否则,则启用亮色模式。 -
我们将刚刚创建的事件处理程序放置在
onChange
属性上,以便每次复选框发生变化时都会触发该事件处理程序。我们还使用defaultDark
之前创建的布尔值来确定复选框是否默认启用。
添加测试(可选)
在将此组件添加到我们的应用程序之前,我们可以编写一些测试以确保它按预期工作。
Create React App预装了React Testing Library。它会自动获取.test.tsx
你创建的任何文件。
src/DarkMode.test.tsx
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import DarkMode from "./DarkMode";
// 1
test("renders dark mode component", () => {
render(<DarkMode />);
// 2
const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
expect(inputElement).toBeInTheDocument();
});
// 3
test("toggles dark mode", () => {
render(<DarkMode />);
const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
// 4
expect(inputElement.checked).toEqual(false);
fireEvent.click(inputElement);
expect(inputElement.checked).toEqual(true);
// 5
expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
});
-
一个简单的测试来确保组件呈现。
-
输入具有的角色,
checkbox
因此我们希望能够通过该角色找到元素。 -
测试以确保在复选框切换时组件确实激活了暗模式
-
使用测试库的
fireEvent
功能,我们可以模拟点击输入。我们在点击之前断言它不应该被检查,然后在点击之后它应该被检查。 -
这个组件在设计上确实存在副作用,而这正是最终断言想要检测的。虽然该组件只是一个用于输入的小容器,但它被设计为将
data-theme
属性应用于根<html>
元素。该元素可以通过 JavaScript 变量直接访问document.documentElement
。我们在这里检查dark
元素被点击后,值是否被应用到属性上。
如果使用默认的 CRA 设置(或者您已自定义配置它),我们可以使用以下命令运行测试:
npm run test
并得到我们的结果:
为应用程序添加暗黑模式
下面我只是导入并添加了<DarkMode />
运行Create React App时创建的默认 App 模板。
src/App.tsx
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import DarkMode from "./DarkMode";
function App() {
return (
<div className="App">
<header className="App-header">
<DarkMode />
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
最后,我们需要更新 CRA 设置中包含的默认 CSS,否则某些颜色/背景颜色值将覆盖我们的主题变量。
App.css
以下示例是颜色值被注释掉的默认版本。如果您愿意,可以将其完全删除。
src/App.css
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
/* background-color: #282c34; */
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
/* color: white; */
}
.App-link {
/* color: #61dafb; */
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
如果您遵循所有步骤,您将获得一个具有您自己的自定义<DarkMode />
组件的功能强大的应用程序。
设置首选配色方案
我们提到过,该应用程序支持用户浏览器配置首选配色方案,但实际上我们并没有解释如何设置该值。
不幸的是,浏览器并不容易做到这一点,但可以在 Chrome 或 Firefox 中按照以下步骤实现:
火狐
about:config
在导航栏中输入- 如果不存在,则创建一个名为的值
ui.systemUsesDarkTheme
并将其设置为Number
- 将数字设置为 1
dark
或 0light
铬合金
- 打开开发者工具(F12)
...
单击工具右上角的省略号图标- 点击更多工具->渲染
- 在“模拟 CSS 媒体”下选择“prefers-color-scheme: dark”
Chrome 有点棘手,所以这里有一个屏幕截图,显示了在哪里可以找到它:
总结
希望你喜欢本教程并从中有所收获!你可能已经注意到,虽然本教程是从React 的角度编写的,但我们使用的几乎所有代码都可以独立于 React 运行。
下次使用原生 HTML/CSS,甚至其他框架时,不妨试试这个!你会发现,只需稍加修改,这段代码就可以在任何地方重复使用。
请查看我的其他学习教程。如果您觉得其中有任何内容有用,请随时发表评论或提出问题并与他人分享:
想要了解更多类似教程,请在 Twitter 上关注我@eagleson_alex
文章来源:https://dev.to/alexeagleson/how-to-create-a-dark-mode-component-in-react-3ibg