使用配色方案在 CSS 中创建亮模式和暗模式
允许用户在网站的亮色调和暗色调方案之间切换,是一项颇受欢迎的功能。你常常会看到一个小太阳或月亮图标,借助它就能在亮模式和暗色调方案之间来回切换。部分操作系统和用户代理也具备激活暗色调方案的功能,网站会识别并应用该方案。
实际上,这一功能非常受欢迎,以至于市面上存在许多浏览器扩展程序,允许用户强制网站以特定的配色方案显示,其中最受欢迎的是将网站设置为暗模式。网站采用暗色调方案有助于提高可读性,尤其对于患有多动症和类似疾病的人而言。
就我个人而言,网站采用暗色调方案意味着我可以在半夜查看内容,既不会让眼睛疲劳,也不会打扰到其他人。我经常失眠,所以能够在黑暗中阅读文章对我来说相当重要。
本周我正在研究如何运用配色方案,将一个网站转换为支持亮模式和暗模式切换。结果发现,为 CSS 主题添加暗模式相当简单,但要以可维护的方式实现,确实需要进行一些规划和预先思考。
在本文中,我们将探讨什么是配色方案,如何触发和检测它们,以及如何组织你的主题以充分利用它们。同时,我公司在 Drupal 开发、Drupal 模块开发以及 Drupal 升级等领域经验丰富,对于 Drupal11 的相关技术也有深入研究,下面介绍的知识在 Drupal 相关项目中也可能会有应用。
你可以使用此菜单跳转到相关部分。
- color-scheme 属性
- 检测配色方案
- 从配色方案中选择颜色
- 图片处理
- 用户对配色方案的选择
- 过渡效果
- 技巧提示
- 总结
- 参考文献
一、color-scheme 属性
color-scheme 属性用于告知元素可用的配色方案。这是我们设置页面配色方案时的主要属性(因此得名),操作系统和用户代理可以更新该属性,为页面选择不同的配色方案。
我们可以将此属性设置为以下选项之一。
normal- 这是该属性的默认设置。元素将使用页面配色方案设置的颜色(在页面的元数据中设置)。如果没有可用的配色方案,则颜色将取自页面的样式表。light- 元素将使用亮色调方案进行渲染。dark- 元素将使用暗色调方案进行渲染。only- 此设置通过设置only light或only dark来防止用户代理覆盖元素的配色方案。我们可以使用此设置来防止页面上的某个元素根据页面的配色方案设置改变颜色。
我们还可以将亮和暗值组合成一个选项,并在亮和暗配色方案之间创建优先级。例如,将值设置为 light dark 意味着页面应优先使用亮色调方案,如果亮模式不可用,则回退到暗色调方案。
p {
color-scheme: light dark;
}
在选择配色方案时,用户代理往往还会更改页面的以下方面以匹配可用的配色方案。
- 画布的背景,在普通或亮模式下设置为白色,在暗模式下设置为黑色。
- 浏览器中用户界面元素(如滚动条)的默认颜色。
- 表单元素的默认颜色。
- 浏览器提供的页面上其他元素的默认颜色。这包括拼写检查下划线、选中文本颜色、缺失图片图标等。
此值由操作系统或用户代理的设置定义。
如果你想设置全局配色方案,最好的做法是在样式的 :root 元素中设置此属性。
:root {
color-scheme: light dark;
}
通过在 :root 元素中设置此属性,我们告诉整个页面的每个元素都有亮和暗两种配色方案。不仅如此,我们实际上让操作系统为整个页面选择配色方案。
color-scheme 属性被所有浏览器(包括移动浏览器)广泛支持,因此可以安全地将其作为网站配色方案的核心原则。
除了在 CSS 中添加此属性外,在页面的 <head> 标签中设置 <meta name="color-scheme"> 元标签也是个好主意,因为这样可以防止在样式加载完成并选择配色方案信息后页面闪烁。
<meta name="color-scheme" content="dark light" />
在样式中设置此属性可能会更改页面上的一些颜色,因此让我们来看看如何检测配色方案并根据该信息更改元素的颜色。
二、检测配色方案
既然我们已经确定了主题使用配色方案,那么我们需要一种方法来检测它,以便使主题适应正确的方案。这可以通过 prefers-color-scheme 媒体查询来完成,我们可以在其中指定要检测的方案。
例如,如果我们想为暗色调方案应用一些自定义样式,可以这样做。
@media (prefers-color-scheme: dark) {
}
反之,当然就是设置亮模式。
@media (prefers-color-scheme: light) {
}
不过,你并不总是需要同时使用这两个查询。基本思路是,如果将 color-scheme 属性设置为 light dark,那么 “亮” 方案将成为默认方案,然后使用 “暗” 媒体查询覆盖主题中设置的颜色。
三、从配色方案中选择颜色
我们有几种选择来为不同的元素选择颜色。
最简单的方法是将一些颜色设置为亮色调方案,然后使用 prefers-color-scheme 媒体查询切换到暗色调方案。在以下示例中,我们将段落标签的文本颜色设置为白色,背景设置为黑色,然后在媒体查询中反转这些设置。
p {
color: #000;
background-color: #fff;
}
@media (prefers-color-scheme: dark) {
p {
color: #fff;
background-color: #000;
}
}
我们还可以使用 CSS 内置的 light-dark() 函数,该函数使我们能够选择合适的颜色,而无需使用媒体查询来检测当前使用的配色方案。该函数接受两个参数,即亮颜色和暗颜色。
只有当我们首先将 color-scheme 属性的值设置为 “light dark” 时,才能使用此函数,之后 CSS 会从这两个选项中为我们选择正确的颜色。
以下示例展示了如何使用 light-dark() 函数为段落标签的文本和背景选择合适的颜色。
:root {
color-scheme: light dark;
}
p {
color: light-dark(#000, #fff);
background-color: light-dark(#fff, #000);
}
与其在整个网站中复制和粘贴这些值,不如将这些值抽象为 CSS 变量。我们可以从抽象单个颜色开始。
在下面的示例中,我们根据变量所属的配色方案来命名(如果未选择配色方案,则命名为 normal)。
:root {
--text: #000;
--background: #fff;
--text-dark: #fff;
--background-dark: #000;
}
p {
color: light-dark(var(--text), var(--text-dark));
background-color: light-dark(var(--background), var(--background-dark));
}
另一种方法是将 light-dark() 函数的结果抽象为一个变量,并将该变量传递给相关属性。这样做可以让我们只在一个地方更改颜色,而不必在 light-dark() 函数调用中查找该颜色的所有实例。
:root {
--text: #000;
--background: #fff;
--text-dark: #fff;
--background-dark: #000;
--light-dark-text: light-dark(var(--text), var(--text-dark));
--light-dark-background: light-dark(var(--background), var(--background-dark));
}
p {
color: var(--light-dark-text);
background-color: var(--light-dark-background);
}
从这些示例中可以看出,只需添加几行 CSS,我们就可以让操作系统或用户代理层面设置配色方案偏好。这意味着如果用户代理设置为暗模式,用户将自动看到网站的暗色调,而无需他们进行任何更改或选择。
本文后面将讨论如何允许用户为网站选择自己的偏好。
四、图片处理
在处理配色方案时,图片的使用可能会有点棘手。如果你为亮色调方案上传了一张图片,那么在暗色调方案中,这张图片会显得非常亮,因为它周围是深色背景,会更加突出。有几种方法可以解决这个问题,让我们来看看你可能会采用的几种方法。
我们可以尝试的第一种方法是为图片应用一个不透明度层。
@media (prefers-color-scheme: dark) {
img:not([src*=".svg"]) {
opacity: 80%;
}
}
这会使大多数图片看起来相当暗淡,因此我们可以允许用户通过将鼠标悬停在图片上来查看图片的全亮版本。
我们需要为桌面用户添加 :hover 事件,为移动用户添加 :active 事件。
@media (prefers-color-scheme: dark) {
img:not([src*=".svg"]) {
opacity: 80%;
transition-duration: 400ms;
transition-property: opacity;
}
img:not([src*=".svg"]):hover, img:not([src*=".svg"]):active {
opacity: 100%;
}
}
现在,如果配色方案为暗模式,图片会稍微变暗,但用户可以将鼠标悬停在上面以查看全亮版本。
如果你无法在 CSS 中更改图片,另一种方法是使用响应式图片。使用 picture 元素,我们可以设置多个不同的图片,然后根据当前的配色方案显示相应的图片。我们可以为每个 source 元素提供一个 media 属性,该属性会告诉浏览器为该位置使用哪张图片。
以下示例展示了这一方法的实际应用,亮模式图片作为默认图片,而当用户选择暗色调方案时使用暗模式图片。
<picture>
<source srcset="image-dark.png" media="(prefers-color-scheme: dark)" />
<source srcset="image-light.png" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" />
<img src="image-light.png" />
</picture>
对于内容图片来说,这种方法不太实用,因为你需要为网站上的每张图片上传多个版本。
功能性图片
功能性图片是指那些表明某个元素具有某种功能的图标和其他图形。例如,外部链接旁边的小图标,用于显示用户点击后将离开当前网站,甚至是显示当前使用的配色方案的图标。
所有这些图片都需要有亮和暗两种配色方案的替代版本。假设这些图片是通过 background-image 属性添加的,那么根据当前的配色方案更改这些属性值就可以实现图片的切换。
.some-class {
background-image: url("./images/icon-link-light.svg");
}
@media (prefers-color-scheme: dark) {
.some-class {
background-image: url("./images/icon-link-dark.svg");
}
}
使用这种机制,你可以确保网站上的图标仍然能够正常工作。这与我们在网站其他地方更改元素颜色所使用的机制基本相同。
五、用户对配色方案的选择
现在我们已经设置好了配色方案,还需要一种方法让用户能够忽略系统的设置并选择自己的方案。
经过一些实验,我发现最可靠的方法是将一个类注入到页面的 body 标签中,并强制配色方案为亮模式或暗模式。实现这一功能的样式非常简单,我们只需检测所使用的类,并使用 color-scheme 属性设置配色方案。
.light-theme {
color-scheme: light;
}
.dark-theme {
color-scheme: dark;
}
设置好这些后,我们只需注入这个类就可以更改页面上的颜色。light-dark() 函数在这些更改之后仍然可以正常工作,但你仍然需要将根元素的配色方案设置为 “light dark”,以便能够选择正确的颜色。
为了让用户能够选择自己的偏好,我们需要添加一个按钮,以下是一个可以用于实现此功能的典型按钮。
<div>
<button class="light-dark-toggle" aria-label="Switch color theme"><span></span></button>
</div>
div 元素的存在是为了让你可以将图标放置在页面的其他元素中,而不必调整按钮组件的样式。为按钮添加样式时,我们还注入一个背景图像,作为用户的亮/暗指示器。
.light-dark-toggle {
background: none;
border: none;
span::before {
content: "";
background-image: url("./images/icons/sun.svg");
width: 25px;
display: block;
height: 25px;
cursor: pointer;
}
}
为了实现这个目的,我公司找到了一些免费的开源太阳和月亮 SVG 图像,这些图像并不难找到。
为了更改按钮的背景图像,我们需要添加以下样式。这告诉浏览器,如果用户更喜欢暗色调方案 并且 亮模式未启用,则将按钮中的图标更改为月亮。这是必要的,这样如果 prefers-color-scheme 媒体查询告诉我们用户想要查看暗模式,但他们选择了亮模式,我们就可以尊重他们的请求。
@media (prefers-color-scheme: dark) {
body:not(.light-theme) {
.light-dark-toggle {
span::before {
background-image: url("./images/icons/moon.svg");
}
}
}
}
让我们为按钮创建一个操作。我们需要做的是查找当前应用于页面的类,如果未设置,则回退到页面的 prefers-color-scheme 属性。获取所需信息后,我们将正确的类应用到页面的 body 标签,从而更改所选的配色方案。
完成此操作后,我们还将该信息存储在本地存储中。这样,当用户切换到不同的页面时,我们可以从本地存储中提取该值,并确保他们所选的配色方案也应用到网站的其他页面上。
const btn = document.querySelector(".light-dark-toggle");
btn.addEventListener("click", function lightDarkClickAction() {
const hasDarkClass = document.body.classList.contains("dark-theme");
const hasLightClass = document.body.classList.contains("light-theme");
if (hasDarkClass === false && hasLightClass === false) {
// 用户未设置主题类,因此使用他们的操作系统设置来添加。
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
) {
// 用户更喜欢暗模式,切换到亮主题。
document.body.classList.add("light-theme");
} else {
// 用户更喜欢亮模式,切换到暗主题。
document.body.classList.add("dark-theme");
}
} else if (hasDarkClass) {
// 暗模式类已设置,切换到亮模式。
document.body.classList.remove("dark-theme");
document.body.classList.add("light-theme");
} else if (hasLightClass) {
// 亮模式类已设置,切换到暗模式。
document.body.classList.add("dark-theme");
document.body.classList.remove("light-theme");
}
let theme = "light";
if (document.body.classList.contains("dark-theme")) {
theme = "dark";
}
localStorage.setItem("theme", theme);
});
页面加载时,我们需要确保在本地存储中查找用户的偏好,并根据该值应用相应的 body 类。
function colourSchemeInit() {
if (localStorage.getItem("theme") === "dark") {
// 使用用户的暗主题偏好覆盖类。
document.body.classList.add("dark-theme");
} else if (localStorage.getItem("theme") === "light") {
// 使用用户的亮主题偏好覆盖类。
document.body.classList.add("light-theme");
}
}
colourSchemeInit();
总之,当用户加载页面时,我们会查找之前设置的配色方案。如果已设置,则将相应的类应用到 body 元素上,以强制使用该配色方案。如果用户再次点击配色方案切换按钮,我们就知道他们之前的选择,并切换到另一个版本。
如果你还想允许用户取消他们的偏好设置,可以添加一个额外的按钮,让用户能够进行控制。
<div>
<button class="light-dark-clear" aria-label="Clear color theme">Clear colour preferences</button>
</div>
按钮的点击操作,仅清除主题设置的本地存储。
const clearButton = document.querySelector(".light-dark-clear");
clearButton.addEventListener("click", function clearButtonAction() {
// 移除已设置的类。
document.body.classList.remove("light-theme");
document.body.classList.remove("dark-theme");
// 移除本地存储。
localStorage.removeItem("theme");
});
这将清除本地存储,并将主题重置为用户最初的操作系统或用户代理的默认设置。
请记住,这是用户的偏好设置,你应该谨慎操作,避免在未经用户直接参与的情况下更改他们的选择。在用户不知情的情况下设置页面的配色方案可能会导致用户感到困扰和沮丧。
六、过渡效果
为了增加一些趣味性,你可以为主题的颜色添加过渡效果,这样当用户点击主题切换按钮时,他们不会看到屏幕立即从一种颜色切换到另一种颜色,而是看到颜色从一种配色方案渐变为另一种配色方案。
我们可以通过为网站上的每个元素应用 transition-duration 样式来实现这个过渡效果,因为该样式会在不同颜色属性的颜色发生变化时生效。


