Web编程技术营地
研究、演示、创新

CSS亮黑主题切换

撰写时间:2025-03-03

修订时间:2025-12-18

无任何CSS设定的输出

下面结果面板中的内容无任何CSS设置。

This is some sample paragraph texts.

因为没有任何CSS设置,因此结果面板默认情况是白底黑字。在系统中切换颜色模式,结果面板中不会有任何变化。

color-scheme

自动设置主题颜色

:root { color-scheme: light dark; }

This is some sample paragraph texts.

点击Run按钮,当设置了color-scheme属性值后,在系统中切换颜色主题,则结果面板的内容能自动响应系统的颜色模式切换。

这一点也说明了Web Components下面的各个子标签(被ShadowRoot所隔离),也能接收到系统发来的信息。

我们在根目录下设置了color-scheme,则根目录下所有的子标签,都能响应主题颜色变换。当模式为light时,所有子标签自动变成白底黑字;当模式为dark时,所有子标签自动变成黑底白字。

只设置亮色主题颜色

可以只选择一种颜色模式。

:root { color-scheme: light; }

This is some sample paragraph texts.

上面设置,则不管系统的主题颜色是什么,都只会自动使用light模式的设置,即自动设置为白底黑字。

只设置暗色主题颜色

下面只使用dark模式。

:root { color-scheme: dark; }

This is some sample paragraph texts.

分别设置主题颜色

根据规范,可以为各个子标签分别设置不同的主题颜色。

:root { color-scheme: dark light; } #p1 { color-scheme: dark; } #p2 { color-scheme: light; }

This is paragraph 1.

This is paragraph 2.

ChromeSafari目前只支持在:root标签中设置color-schme,从而使这种技术形同虚设。

用户设置优先

:root { color-scheme: dark light; } #p1 { color: orange; background-color: gray; }

This is paragraph 1.

This is paragraph 2.

2p,第1p设置了灰底橙字,则用户设置优先,不管系统的主题颜色是什么,p1永远都是灰底橙字。而p2未进行任何自定义设置,则仍由浏览器根据系统主题颜色来自动设置样式。

也即说,使用color-scheme,一旦我们自行设定,浏览器就不再接管。

prefers-color-scheme

使用媒介查询技术中的prefers-color-scheme,网页内容照样可以收到系统主题颜色变换的信息,并且可以只针对亮色主题或暗黑主题进行单独的设置。

@media(prefers-color-scheme: light) { body { color: #555; background-color: #ccc; } } @media(prefers-color-scheme: dark) { body { color: #ACB7C4; background-color: #2B2B2B; } }

This is paragraph 1.

This is paragraph 2.

这样,当系统发来当前颜色主题的信息时,分别调用相应的设置值,从而在网页中较为智能地实现了颜色主题的切换。

prefers-color-scheme可以配合color-scheme同时使用。

:root { color-scheme: dark light; } @media(prefers-color-scheme: light) { #p1 { color: #555; background-color: #ccc; } } @media(prefers-color-scheme: dark) { #p2 { color: #ACB7C4; background-color: #2B2B2B; } }

This is paragraph 1.

This is paragraph 2.

prefers-color-scheme的设置中,只对p1进行了设置。由于p2未设置,则由color-scheme为其自动设置。

但由于CSS属性值具有继承性,我们一般在父标签上设置一个通用值而由子元素自动继承,因此这种同时设置上述两个CSS属性值的场合并不多见。

light-dark()函数

使用prefers-color-scheme最大的问题,是我们必须同时为亮色与暗黑主题分别设置不同的值,并且,同一标签在亮色与暗黑主题的不同的值,散列于不同的代码块中,在维护时,当需要修改某个标签的值时,得在两个代码惨块中同时修改。因此,代码量不仅成倍增长,维护更为不便。

CSS Color Module Level 5草案引入了light-dark函数,提供了更为简洁的颜色主题适配方案。

:root { color-scheme: dark light; } body { color: light-dark(#555, #ACB7C4); background-color: light-dark(#ccc, #2B2B2B); }

This is paragraph 1.

This is paragraph 2.

light-dark函数必须配合color-scheme来使用,后者用以接收从系统传来的颜色主题切换消息。

这样,代码简洁了不少,且由于相关设置值都集中置于特定标签后面,维护起来非常方便。从这点上来讲,它在一定程度上消除了大量使用CSS变量的必要性。

Chrome支持,Safari 18.2默认打开了light-dark函数的功能开关。因此,均可放心使用。

许多网站目前仍使用以下格式来编写CSS代码:

:root { color-scheme: dark light; } .light body { color: #555; background-color: #ccc; } .dark body { color: #ACB7C4; background-color: #2B2B2B; }

This is paragraph 1.

This is paragraph 2.

这些网站会自动将body的父元素htmlclass属性值设置为lightdark,以便让上述CSS生效。

其缺点是,为body设置颜色的代码明显分成两小块,维护困难。对于这种代码,在尊重其原有DOM结构的基础上,我们也可改写为Nested CSS的形式:

:root { color-scheme: dark light; } body { .light & { color: #555; background-color: #ccc; } .dark & { color: #ACB7C4; background-color: #2B2B2B; } }

This is paragraph 1.

This is paragraph 2.

这种效果类似于使用light-dark函数,特定元素的所有设置颜色的代码均置于该元素的选择器之下,维护很方便。

matchMedia

除了CSS设置外,我们还可在JavaScript中调用window.matchMedia方法来查询在CSS是否已设置特定的系统颜色主题响应。

let mediaQueryList = matchMedia('(prefers-color-scheme: light)'); pc.log(mediaQueryList); pc.log(mediaQueryList.media); mediaQueryList.onchange = () => { pc.log('%s', getCurrColorScheme()); }; function getCurrColorScheme() { if (mediaQueryList.matches) { return 'light'; } else { return 'dark'; } }
:root { color-scheme: dark light; } body { color: light-dark(#000, #ACB7C4); background-color: light-dark(#FFF, #2B2B2B); }

Toggle color scheme in system to change the color scheme in HTML page.

matchMedia返回一个MediaQueryList对象,其media属性值为我们所要查询的字符串,如果已设置,则matches属性值为true,否则为false

可以为MediaQueryList对象设置onchange响应事件,这样,当系统颜色主题发生改变时,我们可进一步响应此事件。但一般情况下,当我们在CSS设置了:

:root { color-scheme: dark light; } body { color: light-dark(#000, #ACB7C4); background-color: light-dark(#FFF, #2B2B2B); }

之后,当系统颜色主题发生改变时,浏览器将自动先行收到消息并应用我们已经设置好的相应的颜色,然后再响应MediaQueryListonchange事件,因此我们应尽可能将自动改变颜色的代码放在CSSlight-dark函数中,这样既便捷且易维护。只有在我们需要相对比较复杂的业务逻辑判断时,才需要自行响应其onchange事件。

上面的代码,如果没有设置onchange事件,已经可以自动响应系统颜色主题的切换而改变颜色。在该事件中,我们只是简单地打印出当前颜色主题而已。

允许用户指定颜色主题

以上,当我们在系统中选定了颜色主题后,相应主题的CSS样式将自动被调用。

但我们应当允许用户指定颜色主题,以覆盖系统当前颜色主题。

主要有2种方法可实现此目标。第一种是当我们收到系统颜色主题变更通知时,为htmlbody标签的class设置相应的lightdark值,然后,为它们的各个子标签分别进行相应的颜色设置:

:root { color-scheme: dark light; } .light body { color: #000; background-color: #FFF; } .light button { ... } .dark body { color: #ACB7C4; background-color: #2B2B2B; } .dark button { ... }

但更简便的方法是,通过JavaScript直接修改color-scheme的值即可。

当我们允许用户可自行选择颜色主题时,又分为两种情况。第一种情况,允许用户选择根据系统颜色主题来自动切换网页颜色;第二种情况,不管系统是什么颜色主题,完全由用户自行决定亮色或暗色。下面代码反映了这两种情况。

function getSystemColorScheme() { if (matchMedia('(prefers-color-scheme: light)').matches) { return 'light'; } else { return 'dark'; } } let prevSelectedScheme; document.querySelector('#by-system').onclick = (evt) => { document.documentElement.style.colorScheme = 'light dark'; prevSelectedScheme = ''; }; document.querySelector('#by-user').onclick = (evt) => { if (!prevSelectedScheme) { prevSelectedScheme = getSystemColorScheme() === 'light' ? 'dark' : 'light'; } else { prevSelectedScheme = prevSelectedScheme === 'light' ? 'dark' : 'light'; } document.documentElement.style.colorScheme = prevSelectedScheme; };
:root { color-scheme: dark light; } body { color: light-dark(#000, #ACB7C4); background-color: light-dark(#FFF, #2B2B2B); } button { padding: 0.375rem 0.75rem; border: 1px solid gray; border-radius: 0.375rem; font-size: 0.725rem; font-weight: 400; line-height: 1.5; margin-right: 1em; cursor: pointer; color: #fff; background-color: #0d6efd; &:hover { color: #FFF; background-color: #0b5ed7; } &:active { box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } }

Hello, color scheme.

使用第二种方式,配合CSSlight-dark函数,两套颜色主题的代码完全集中于一处,代码维护非常方便。

一个完整的例子

综合应用上述各节的技术,本站使用iframe编写了一个完整且可独立运行的例子,具体详见在 iframe 中玩转 Color Scheme

参考资源

  1. CSS Color Adjustment Module Level 1
  2. Media Queries Level 5
  3. CSS Color Module Level 4
  4. CSS Color Module Level 5