随着我们在网络上向更好、更方便的用户体验迈进,黑暗模式已经成为网络应用的一个主流功能。当涉及到黑暗模式的开发时,它不仅仅是添加一个简单的切换按钮和管理CSS变量。这里我们将讨论在React应用程序中创建一个完整的黑暗模式体验。
以下是我们将讨论的内容。
- 使用系统设置
- 使用CSS变量管理主题
- 使用react-toggle实现颜色方案的切换
- 使用use-persisted-state存储用户喜欢的模式
- 选择适合更多受众的颜色组合
- 处理黑暗模式下的图像
使用系统设置
当用户登陆他们的网站时,没有人想伤害他们的眼睛!最好的做法是根据设备的设置来设置应用程序的主题。CSS媒体查询,一般以响应式设计的用法而闻名,也帮助我们检查其他设备的特性。
在这里,我们将使用 [prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)来提供dark 、light ,或no-preference ,基于设备选择的颜色方案。
即使是最简单的形式,这也可以帮助我们为网络应用添加一个黑暗模式。
@media (prefers-color-scheme: dark) {
background-color: #1F2023
color: #DADADA
}
像其他媒体查询一样,当设备的颜色方案被设置为深色时,该块的样式将被应用。把它放在一些组件的样式中,会看起来像这样。
import { styled } from '@linaria/react';
const Text = styled.p`
margin: 12px;
color: #1F2023;
background-color: #FAFAFA;
@media (prefers-color-scheme: dark) {
background-color: #1F2023
color: #DADADA
}
`;
这在开始时是好的,但不能一直在每个组件中添加这些样式。在这种情况下,CSS变量就是答案了。
使用CSS变量管理主题
CSS变量是网页样式设计中缺失很久很久的一个工具。现在它们可以在所有的浏览器上使用,CSS变得更加有趣而不那么痛苦了。
CSS变量的作用范围是它们所声明的元素,并参与级联(即元素是子元素的覆盖值)。
我们可以利用CSS变量来定义我们应用程序的主题。这里有一个小片段来回顾一下CSS变量是如何声明的。
body {
--color-background: #FAFAFA;
--color-foreground: #1F2023;
}
为了在我们的组件中使用这些变量,我们将把颜色代码与变量交换。
const Text = styled.p`
margin: 12px;
color: var(--color-foreground);
background-color: var(--color-background);
`;
现在,我们的颜色是通过CSS变量定义的,我们可以在我们的HTML树的顶部改变数值(例如,<body> ),并且可以在所有的元素上看到反映。
body {
--color-background: #FAFAFA;
--color-foreground: #1F2023;
@media (prefers-color-scheme: dark) {
--color-background: #1F2023;
--color-foreground: #EFEFEF;
}
}
实现颜色方案的切换
在这一点上,我们有一个最简单的解决方案,它基于设备的偏好而工作。现在,我们必须为那些不支持黑暗模式的设备进行调整。
在这种情况下,我们必须让用户很容易为我们的网络应用设置他们的偏好。我选择了react-toggle,以使我们的解决方案在a11y时更有优势,同时具有良好的美感。这可以通过简单的button 和useState 来实现。
下面是我们的切换组件的样子。
import React, { useState } from "react";
import Toggle from "react-toggle";
export const DarkModeToggle: React.FC = () => {
const [isDark, setIsDark] = useState<boolean>(true);
return (
<Toggle
className="dark-mode-toggle"
checked={isDark}
onChange={({ target }) => setIsDark(target.checked)}
icons={{ checked: "🌙", unchecked: "🔆" }}
aria-label="Dark mode toggle"
/>
);
};
这个组件将持有用户选择的模式,但默认值呢?我们的CSS解决方案尊重设备的偏好。为了在我们的react组件中提取媒体查询结果,我们将利用react-responsive。在引擎盖下,它使用 [Window.matchMedia()](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)并在查询的输出改变时重新显示我们的组件。
该按钮的更新版本看起来像下面这样。
import React, { useState } from "react";
import Toggle from "react-toggle";
export const DarkModeToggle: React.FC = () => {
const [isDark, setIsDark] = useState<boolean>(true);
const systemPrefersDark = useMediaQuery(
{
query: '(prefers-color-scheme: dark)',
},
undefined,
(isSystemDark: boolean) => setIsDark(isSystemDark)
);
return (
<Toggle
className="dark-mode-toggle"
checked={isDark}
onChange={({ target }) => setIsDark(target.checked)}
icons={{ checked: "🌙", unchecked: "🔆" }}
aria-label="Dark mode toggle"
/>
);
};
useMediaQuery 钩子接受一个查询、初始值和一个onChange 处理程序,该处理程序在查询的输出发生变化时被触发。
模拟浏览器的黑暗模式
现在我们的组件将与设备的偏好同步,它的值也将相应地更新。但我们如何测试它是否做对了呢?
感谢对开发者友好的浏览器,我们可以从浏览器检查器中模拟设备首选项;这里是它在Firefox中的样子。
现在是时候把我们的切换组件的状态变化与CSS联系起来了。这可以通过几种不同的技术来完成。在这里,我们选择了最简单的方法:在根HTML标签上添加一个类,让CSS变量来完成剩下的工作。
为了适应这一点,我们将更新我们的body标签的CSS。
body {
--color-background: #FAFAFA;
--color-foreground: #1F2023;
&.dark {
--color-background: #1F2023;
--color-foreground: #EFEFEF;
}
}
这里是我们根据状态来添加和删除类的效果。
...
useEffect(() => {
if (isDark) {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
}, [isDark]);
...
使用use-persisted-state来存储用户的首选模式
如果我们把用户的首选颜色方案保留在组件的状态中,可能会出现问题,因为我们将无法在这个组件之外获得这些值。而且,一旦我们的应用程序再次被安装,它就会消失。这两个问题都可以用不同的方法解决,包括用React Context或其他的状态管理方法。
另一个解决方案是使用use-persisted-state。这将帮助我们满足所有的要求。它将状态与localStorage ,并在应用程序在浏览器的不同标签中打开时保持状态同步。
我们现在可以将我们的黑暗模式状态移到一个自定义钩子中,该钩子封装了所有与媒体查询和持久化状态有关的逻辑。下面是这个钩子的样子。
import { useEffect, useMemo } from 'react';
import { useMediaQuery } from 'react-responsive';
import createPersistedState from 'use-persisted-state';
const useColorSchemeState = createPersistedState('colorScheme');
export function useColorScheme(): {
isDark: boolean;
setIsDark: (value: boolean) => void;
} {
const systemPrefersDark = useMediaQuery(
{
query: '(prefers-color-scheme: dark)',
},
undefined,
);
const [isDark, setIsDark] = useColorSchemeState<boolean>();
const value = useMemo(() => isDark === undefined ? !!systemPrefersDark : isDark,
[isDark, systemPrefersDark])
useEffect(() => {
if (value) {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
}, [value]);
return {
isDark: value,
setIsDark,
};
}
现在,切换按钮组件将变得更加简单。
/**
*
* ColorSchemeToggle
*
*/
import Toggle from 'react-toggle';
import { useColorScheme } from 'platform/ColorScheme';
import { DarkToggle } from './Styled';
const ColorSchemeToggle: React.FC = () => {
const { value, setValue } = useColorScheme();
return (
<DarkToggle>
<Toggle
checked={value === 'dark'}
onChange={(event) => setValue(event.target.checked ? 'dark' : 'light')}
icons={{ checked: '🌙', unchecked: '🔆' }}
aria-label="Dark mode"
/>
</DarkToggle>
);
};
export default ColorSchemeToggle;
选择黑暗主题的颜色
虽然黑暗模式本身可以被认为是一个无障碍功能,但我们应该专注于让更多的人可以使用这个功能。
我们在演示中利用了react-toggle来确保用于改变颜色方案的按钮遵循所有a11y标准。另一个重要的部分是在深色和浅色模式下对背景和前景颜色的选择。在我看来,colors.review是一个测试颜色之间对比度的很好的工具;拥有AAA级的颜色使我们的应用程序更容易导航,看起来更舒服。
在黑暗模式下处理图像
为了获得更好的美感,我们的页面通常都有明亮的图片。在黑暗模式下,明亮的图像可能会成为用户的不适。
有几种技术可以避免这些问题,包括为两种模式使用不同的图像和改变SVG图像的颜色。一种方法是在所有图像元素上使用CSS过滤器;当明亮的图像出现在用户的画布上时,这将有助于降低眼睛的疲劳。
为了实现这一点,我们的全局样式将如下所示。
body {
--color-background: #FAFAFA;
--color-foreground: #1F2023;
--image-grayscale: 0;
--image-opacity: 100%;
&.dark {
--color-background: #1F2023;
--color-foreground: #EFEFEF;
--image-grayscale: 50%;
--image-opacity: 90%;
}
}
img,
video {
filter: grayscale(var(--image-grayscale)) opacity(var(--image-opacity));
}
结论
今天,网络应用中的可访问性不仅仅是一种实用性。相反,它是基本要求之一。在这方面,当实施黑暗模式时,应该被视为一个完整的功能,需要非常关注,就像任何其他关键功能一样。
在这篇文章中,我们建立了一个全面实现黑暗模式的方法;如果你觉得我错过了什么,请在评论中告诉我。
The postDark mode in React:深度指南首次出现在LogRocket博客上。