仅用CSS指定每主题颜色的几种方法

Lobsters Hottest 工具

摘要

本文介绍了五种仅用CSS实现每主题颜色(浅色/深色/自动)的技术,无需JavaScript,利用prefers-color-scheme、:has()、CSS变量和color-mix()等特性。

<p><a href="https://lobste.rs/s/budvjr/few_ways_specifying_per_theme_colours">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/05/16 11:11

# 仅用CSS指定每主题颜色的几种方式 —— Chris Morgan 来源:https://chrismorgan.info/css-themed-colours 🗓️2026\-05\-16•标签:/css (https://chrismorgan.info/css),/meta=also (https://chrismorgan.info/meta%3Dalso) 我在搭建这个网站时考虑到了这个问题。实际上是因为我忘了 `light-dark()`;如果早些想起它,我可能就不会折腾出这么多花样了! **我的要求:**(可能与你的要求不符) - 必须支持自动(基于 `prefers-color-scheme`)、浅色和深色三种模式,通过单选按钮选择。 - 必须无需JavaScript即可工作(持久化超出范围)。 ## 假设的基础HTML和CSS 具体如何包含和结构化由你决定,但后续所有示例都基于类似这样的结构: ```html 主题 跟随系统 浅色 深色 ``` 我将假设以下CSS特性可用: 1. `@media (prefers-color-scheme: dark)` 用于自动选择。该特性于2019–2020年推出,通常已足够。 2. `:has()` 用于手动选择,无需额外JavaScript。它较新,支持回溯到2023-12 (https://caniuse.com/css-has);*我*认为它已足够可靠,但如果你不满意,有两种选择: - 只支持一种行为(通常是自动),无JS。这是非常合理的选择。你可以为根元素添加 `light` 或 `dark` 类。 - 将单选按钮改为body的直接子元素,并隐藏它们视觉上,然后使用 `#theme-foo:checked ~ * ...` 代替 `:root:has(#theme-foo:checked) ...`。这比较麻烦,会有一些后果和不便,但通常是可行的。 3. 本文在某些地方也会使用嵌套选择器(2023-12);但如果你愿意,可以很容易地展平。 接下来是五种技术。 ## 1. 硬着头皮全部写出来 老派且冗长。 - 兼容性最好。 - 易于扩展到浅色和深色以外的主题。 - 语法冗长。 - 深色主题的值必须重复。 ```css some-element { color: darkred; :root:has(#theme-dark:checked) & { color: pink; } @media (prefers-color-scheme: dark) { :root:has(#theme-auto:checked) & { color: pink; } } } ``` 通常会有不同的结构,嵌套方式实际上像下一个示例那样反转,但主旨相同。 ## 2. 大量颜色调色板变量 可能是最流行的方法,传统上如此。不过我很惊讶地发现这个实现直到2014–2017年才落地。感觉远不止那么久。但称之为“传统”仍然合理,因为在此之前人们经常使用Sass变量达到类似效果(那时你很少有多主题)。 - 自2017年4月起可用。 - 易于扩展到浅色和深色以外的主题。 - 使用站点的语法很理想。 - 将颜色定义与使用位置分离可能有益也可能有害,可能适合也可能不适合你的项目。它*可能*鼓励一些坏模式和好模式,并且可能会增加一些开销。(我故意说得模糊,这里不展开。) - 深色主题的值必须重复。 声明部分: ```css :root { --color-somepurpose: darkred; --color-another: ...; ⋮ } :root:has(#theme-dark:checked) { --color-somepurpose: pink; --color-another: ...; ⋮ } @media (prefers-color-scheme: dark) { :root:has(#theme-auto:checked) { --color-somepurpose: pink; --color-another: ...; ⋮ } } ``` 添加更多主题很简单。使用位置: ```css some-element { color: var(--color-somepurpose); } ``` 显然,你也可以将变量用于接下来的三种方法。 ## 3. 使用 `color-mix()` 配合每个主题一个变量 我不记得之前见过这种技术(实际上我对`color-mix()`受到的关注之少感到惊讶),但它有效。基本思想是:将颜色定义为所有主题颜色的混合,但用一个变量将其中一个主题设为100%,其余设为0%。(实践中通常只使用两个主题:浅色和深色。) - 自2023年5月起可用。 - 易于扩展到浅色和深色以外的主题。 - 语法相对紧凑。 - 语法可能不常见,但相当直接。遗憾的是目前必需插值方法部分。 - 为实际*混合*颜色/主题开辟了有趣的可能性。`--dark: 50%;` 是什么意思?你可以尝试后果! - 如果你尝试混合颜色和主题,控制插值会非常*痛苦*;你必须弄清楚如何*数学地*表达所需的函数。 这就是我为自己网站采用的技术,因为一旦我意识到潜力,我就想尝试混合主题。我已经开始写更多关于它的内容。(https://chrismorgan.info/interpolating-themes) 它比你想象的更有趣/更复杂。 在样式表开头: ```css :root { --dark: 0%; } :root:has(#theme-dark:checked) { --dark: 100%; } @media (prefers-color-scheme: dark) { :root:has(#theme-auto:checked) { --dark: 100%; } } ``` 然后对每种颜色这样使用 `color-mix()`: ```css some-element { color: color-mix(in oklab, darkred, pink var(--dark)); } ``` 这样在浅色模式下是 `darkred`,深色模式下是 `pink`。大多数情况下你只关心改变*颜色*,但如果你想根据主题改变其他东西,可以将变量设为数字而不是百分比,并使用 `calc()`。(没有考虑过后果——我希望百分比和数字统一,`100%` 等同于 `1`。) ### 语法兼容性陷阱 注意规范 (https://drafts.csswg.org/css-color-5/#color-mix) 允许的内容和 MDN (https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/color-mix) 介绍的内容: 1. 插值方法是可选的,默认是 `oklab`,但这只是2025年末 (https://github.com/w3c/csswg-drafts/issues/10484#issuecomment-3201053810) 才发生的。所有主流浏览器现已支持,但按照我的定义,要到大约一年半后才能放心依赖。所以相当一段时间内,你仍然需要写 `in oklab,`。MDN的浏览器兼容数据缺少这项。(https://github.com/mdn/browser-compat-data/issues/28693) Lightning CSS缺少这项。(https://github.com/parcel-bundler/lightningcss/issues/1224) 2. 规范允许*一个或多个*颜色规范,这有利于混合超过两个主题,但大多数实现仍只支持恰好两个。这个问题*确实*在MDN浏览器兼容数据中跟踪了。(https://caniuse.com/mdn-css_types_color_color-mix_variadic_color_arguments) Lightning CSS缺少它。撰写本文时,只有Firefox提供了这个功能(150,2026-04-21)。 ### 超过两个主题 你可以通过设置更多变量和混合更多颜色来扩展到两个以上主题。定义两个新主题 Grass 和 Ocean: ```css :root { --grass: 0%; --ocean: 0%; } :root:has(#theme-grass:checked) { --grass: 100%; } :root:has(#theme-ocean:checked) { --ocean: 100%; } ``` 然后使用它们: ```css some-element { color: color-mix(in oklab, color-mix(in oklab, color-mix(in oklab, darkred, pink var(--dark)), green var(--grass)), blue var(--ocean)); } ``` 希望在2028年(或一旦Lightning CSS或类似工具支持),你可以简化为以下形式(并且对0%到100%之间的值效果也更好): ```css color: color-mix(in oklab, darkred, pink var(--dark), green var(--grass), blue var(--ocean)); ``` ## 4. `light-dark()` - 自2024年5月起可用(Safari 17.5)。这*勉强*满足我的首选兼容性要求:“Safari两年,其余一年半”。 - 不能用于浅色和深色以外的更多或其他主题。 - 语法紧凑清晰。 首先,你*必须*正确设置 `color-scheme`。所有技术*都*应该这样做,原因包括滚动条和表单元素着色,但对于 `light-dark()` 来说,这是它*根本*工作的必要条件。 ```css :root { color-scheme: light dark; } :root:has(#theme-light:checked) { color-scheme: light; } :root:has(#theme-dark:checked) { color-scheme: dark; } ``` 然后: ```css some-element { color: light-dark(darkred, pink); } ``` ## 5. `if()` - 仅Chromium系列实现(2025-05);Firefox或Safari尚未支持。 - 易于扩展到浅色和深色以外的主题。 - 语法很大程度上符合程序员的期望, - ...但相当冗长。 在样式表开头: ```css :root { --theme: light; } :root:has(#theme-dark:checked) { --theme: dark; } @media (prefers-color-scheme: dark) { :root:has(#theme-auto:checked) { --theme: dark; } } ``` 然后对每种颜色使用: ```css some-element { color: if( style(--theme: light): darkred; style(--theme: dark): pink; ); } ``` 这比 `color-mix()` 灵活性不同:`color-mix()` 仅限于颜色,且允许连续插值;`if()` 适用于所有类型,但仅限于离散值。根据已有示例,添加更多主题很简单。 ## 5.(实际为第六种)暂停的 `@keyframes` 动画 这是一种我偶尔见过的技巧,但我不记得曾见过用于*颜色*。你大概不定义颜色调色板*变量*,而是定义颜色调色板*关键帧规则*(每个使用它的属性一个!),其中“from”表示浅色模式,“to”表示深色模式。然后在使用位置添加一个暂停的动画,在深色模式下应用负延迟将动画置于“to”(深色)值。 - 人们会认为你疯了。他们很有可能是对的。 - 自2020年1月起可用(当Edge采用Chromium时;否则就是2013年7月)。 - 与 `color-mix()` 一样,该技术*引诱*你允许混合颜色/主题! - 如果你真的想混合主题,对于控制插值,没有比这更好的了(几乎就像是为它设计的!)(不过……关键帧并不*总是*比数学函数好。) - 除非你没有其他动画,否则你将被大量重复和极其痛苦的 `animation` 声明组合所困扰。 - 我打赌你会在途中遇到特异性风格的麻烦,因为动画甚至比 `!important` 更强大。 ```css @keyframes color-somepurpose { from { color: darkred; } to { color: pink; } } some-element { animation: 1s paused color-somepurpose forwards; :root:has(#theme-dark:checked) & { animation-delay: -1s; } @media (prefers-color-scheme: dark) { :root:has(#theme-auto:checked) & { animation-delay: -1s; } } } ``` 我不再进一步解释这一点;如果你不理解,可能最好如此——或者将其视为挑战。 最后,作为一个总结,这里有一个比较表格。 | 技术 | 支持时间 | 选择器重复 | 美观/可维护性? | 插值 | 主题 | 使用它暗示什么 | |------|----------|------------|----------------|------|------|---------------| | 手动 | 永远 | 到处 | 笨拙 | 否 | 任意 | 你*只有*一个主题。 | | 变量† | 2017-04 | 仅在定义处 | 除了定义重复外,使用体验很好 | 否 | 任意 | 你相当正常,但不够*时尚*。 | | `color-mix()` | 2023-05 | 否 | 不熟悉,暂时有点冗长 | 是,但非线性时麻烦 | 任意 | 你可能玩得太开心了。 | | `light-dark()` | 2024-05 | 否 | 典范 | 否 | 仅浅色/深色 | 你很时尚。 | | `if()` | 仅Chromium | 否 | 清晰但冗长 | 否 | 任意 | 你*完全*不在乎网页的健康。 | | `@keyframes` | 2020-01 | 远比那糟糕 | 这个单元格太小,无法充分描述噩梦 | 近乎终极灵活性 | 理论上任意(但请别尝试) | 病态天才?那是我善意解读的极限了。 | † 变量应用于手动方式,但你可以将它与 `color-mix()`、`light-dark()` 和 `if()` 一起使用,而且很可能会这样做。`@keyframes` 有点已经包含了自己的替代方案,而且可能比听起来更糟糕。

相似文章

算法主题引擎

Hacker News Top

本文介绍了新的CSS `contrast-color()`函数,该函数允许开发人员自动选择黑色或白色文本,以实现与任何背景颜色的可访问对比度,解决网络上长期存在的低对比度问题,而无需依赖JavaScript。

深色模式的六个层次(2024)

Hacker News Top

一篇探讨网页设计中深色模式实现的六个层次的文章,范围从基础的meta标签方法到使用CSS的高级配色方案切换技术。

使用Web标准实现深色模式

Lobsters Hottest

一篇关于使用Web标准(如color-scheme元标签和prefers-color-scheme媒体查询)在网站上实现深色模式的指南,并包含一个JavaScript切换开关,可尊重用户偏好并将其存储在localStorage中。

使用CSS生成颜色

Lobsters Hottest

本教程介绍如何使用CSS的oklch()函数和相对颜色功能,从单个十六进制代码动态生成完整的调色板,从而简化网站的颜色管理。