这波CSS暗色模式函数能玩出啥花样?,light-dark()的未来远不止明暗两种模式

2,661字
11–17 分钟
in

页面主题是浅色还是深色,以前靠的是媒体查询 prefers-color-scheme 来分别定义两套样式,代码量瞬间翻倍。后来 CSS 出了个 light-dark() 函数,号称能一键切换,当时就觉得这玩意儿挺香的。但用着用着就开始琢磨了,这货就只能管管明和暗吗?要是想弄个高对比度、或者自定义一套“护眼模式”配色,它能不能扛得住?

目录

说白了,light-dark()是个翻译官

light-dark() 这玩意儿,你可以把它想象成一个自带翻译功能的变色龙。它接受两个颜色参数,第一个是给浅色模式准备的,第二个是给深色模式准备的。但光有它俩还不行,得在根元素上配个 color-scheme 属性,告诉浏览器“嘿,我这俩模式都要支持”。

:root {
  /* 允许系统在浅色和深色之间二选一 */
  color-scheme: light dark;
}

.card {
  /* 浅色模式下背景是米色,深色模式下是深灰色 */
  background: light-dark(#f5f0e6, #2d2d2d);
}

就这么一搭配,浏览器就会根据系统的配色偏好,自动把合适的颜色填进去。比起以前写 @media (prefers-color-scheme: dark) 那套,代码量直接砍半,再也不用在各种媒体查询里复制粘贴样式了。

扒开表象,看它咋运作

light-dark() 其实挺“傻白甜”的,它完全听 color-scheme 属性的指挥。这个属性可以接受 lightdark 两个关键词,甚至还能按优先级写多个,比如 color-scheme: dark light; 就表示优先用深色模式。light-dark() 会挨个去匹配,匹配上哪个就输出哪个对应的颜色。

有个坑需要注意,就是 color-scheme 得在根元素上声明才管用,要是写在某个组件上,light-dark() 可不会理它。另外这个函数只能用在颜色的地方,像 font-size 这种尺寸属性,用它直接报错,别想着偷懒拿它去切字体大小。

未来能自定义多少种配色?

既然 light-dark() 需要 color-scheme 撑腰,那要是 color-scheme 能支持更多自定义的模式,是不是就能解锁更多玩法?实际上,CSS 工作组早就想到这茬了,只不过 light-dark() 只是个过渡方案,后面真正的大招叫 schemed-value()

动手搓一个高对比度方案

假如想搞个高对比度模式,传统玩法得用媒体查询 prefers-contrast,又得写一堆判断。有了 schemed-value(),这事儿就简单多了。首先得用 @color-scheme 注册个自定义色彩方案:

/* 注册一个叫“--high-contrast”的高对比度方案 */
@color-scheme --high-contrast {
  base-scheme: dark;        /* 基础色调走深色路线 */
  canvastext: #ffffff;      /* 文字直接用纯白,对比度拉满 */
  canvas: #000000;          /* 背景纯黑,够极端吧 */
  accentcolor: #ffff00;     /* 重点元素用亮黄,保证一眼看到 */
}

注册完之后,还得在根元素上声明,让整个页面都知道这个新家伙:

html {
  /* 把这个自定义方案加进去,同时保留原有的明暗模式 */
  color-scheme: --high-contrast, light, dark;
}

最后在用的时候,就可以通过 schemed-value() 来精细化控制每个地方的配色了:

.header {
  /* 在高对比模式下用黄字黑底,普通模式就按各自默认走 */
  color: schemed-value(--high-contrast, yellow, light blue, darkblue);
}

这个 schemed-value() 第一个参数就是自定义方案的名字,后面跟一串颜色,依次对应 color-scheme 里声明的各个模式。这样一来,一个颜色属性就能同时适配多种配色需求,再也不用写一堆媒体查询来回覆盖了。

自己动手,丰衣足食

除了等官方出 schemed-value(),现在也能用自定义函数玩出类似效果。目前 @function 已经能在 Chromium 内核的浏览器里用了,可以自己封装一个简化版的 light-dark() 扩展:

:root {
  /* 搞个变量存当前模式,默认浅色 */
  --current-scheme: light;
  /* 监听系统变化,动态改这个变量 */
  @media (prefers-color-scheme: dark) {
    --current-scheme: dark;
  }
}

/* 自定义一个函数,专治明暗切换 */
@function --my-light-dark(--light-color, --dark-color) {
  result: if(style(--current-scheme: dark): var(--dark-color); else: var(--light-color));
}

button {
  /* 调用自己的函数,根据系统模式自动变 */
  background: --my-light-dark(#ddd, #333);
}

这套路子的好处是,完全可以按需扩展,比如再加个高对比度检测,让函数支持三个参数。当然坏处也明显,目前自定义函数兼容性还不太行,生产环境得悠着点。

图片也能跟着模式变?

讨论到这儿,其实 light-dark() 的野心远不止颜色。GitHub 上还有个议题在讨论,要不要搞个类似的函数给图片用。比如一个 logo 图,浅色模式下是黑色版本,深色模式下自动换成白色版本,这需求太常见了。

现在要实现得写两套 <picture> 或者用 JS 去换,麻烦得很。要是有个 light-dark-image() 函数,直接这么写就完事了:

.logo {
  /* 根据配色模式自动选图 */
  background-image: light-dark-image(logo-light.svg, logo-dark.svg);
}

这种思路跟 light-dark() 一脉相承,都是让浏览器自己判断该用哪个资源,开发者只需要把两个版本扔给它就行。

对于小白来说,日常开发最稳的玩法还是先用 light-dark() 配合 color-scheme 搞定基本的明暗切换,这俩现在兼容性已经不错了。如果想提前体验更骚的操作,可以在个人项目里尝试自定义函数,或者关注 schemed-value() 的进展,等它进 Baseline 了,就可以大范围开搞了。毕竟从现在的讨论来看,这波新特性并不是要取代 light-dark(),而是给它穿上外挂,让它能应对更多变态的配色需求。