Web前端之样式中的prefers-color-scheme,一套完整的主题系统设计与原理解析
- [什么是 prefers-color-scheme?](#什么是 prefers-color-scheme?)
- [prefers-color-scheme 的底层工作机制](#prefers-color-scheme 的底层工作机制)
- [prefers-color-scheme 切图片案例](#prefers-color-scheme 切图片案例)
- [light-dark() 的真实执行模型](#light-dark() 的真实执行模型)
- [light-dark() 经典案例](#light-dark() 经典案例)
- [prefers-color-scheme 与 light-dark() 的区别](#prefers-color-scheme 与 light-dark() 的区别)
- [color-scheme 才是幕后真正控制者](#color-scheme 才是幕后真正控制者)
- [prefers-color-scheme 、light-dark() 和 color-scheme 三者分别是什么?](#prefers-color-scheme 、light-dark() 和 color-scheme 三者分别是什么?)
- 主题切换到底在解决什么?
- 推荐架构(核心思想)
- 为什么必须这样分层?
- [light-dark() 在架构中的正确定位](#light-dark() 在架构中的正确定位)
- 最终推荐组合(最佳实践)
什么是 prefers-color-scheme?
prefers-color-scheme是 CSS 的媒体特性(Media Feature),用来判断操作系统或浏览器当前的深浅色模式:
系统深色 => dark
系统浅色 => light
它的核心作用是 条件规则切换,可以控制整段 CSS 生效,而不仅仅是单个属性值。
基本用法
css@media (prefers-color-scheme: dark) { .card { background-color: #111111; color: #ffffff; } }
解释
如果系统是深色模式,则上面的规则生效。
如果系统是浅色模式,则规则不生效,card 会使用默认样式。
核心特点
规则级:可以替换整段 CSS,包括图片、布局、边框等。
自动监听系统主题:不需要 JS。
不响应点击:只能响应操作系统主题变化。
prefers-color-scheme 的底层工作机制
它到底"监听"的是什么?
很多文章会说:"监听系统主题",但这不严谨。
更准确的说法是:浏览器在渲染阶段,向 CSS 暴露了一个 environment media feature(环境媒体特性)。
css(prefers-color-scheme: dark)这个值来源于:
操作系统主题 => 浏览器 UI 主题 => 渲染引擎 => CSS 媒体查询
关键点:1、CSS 本身 没有能力读取系统;2、是浏览器把这个信息"注入"到 CSS 环境中
它什么时候触发?
不是"初始化一次",而是动态响应。
当切换系统主题时:
bashOS主题变化 ↓ 浏览器收到事件 ↓ 重新计算媒体查询(media query evaluation) ↓ 触发样式重算(recalc style) ↓ 重新绘制(repaint)
注意:1、不会重新布局(layout)(除非你改了布局属性);2、只是 style + paint 层变化
为什么它是"规则级"能力?
css.card { background-image: A; } @media (prefers-color-scheme: dark) { .card { background-image: B; } }本质不是"修改值",而是:条件性地激活另一组 CSS 规则
浏览器内部等价于
javascriptif (dark) { 使用规则集 B } else { 使用规则集 A }所以它可以做到:
1、换图片 ✔
2、换布局 ✔
3、换字体 ✔
4、换动画 ✔
这就是它和light-dark()的第一层本质差异。
prefers-color-scheme 切图片案例
html
html<div class="card"> <p>切换系统深浅色,观察背景图变化</p> </div>
style
css* { margin: 0px; padding: 0px; box-sizing: border-box; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; } body { width: 100vw; height: 100vh; display: grid; place-items: center; } .card { width: 368px; height: 268px; display: flex; justify-content: center; align-items: center; padding: 8px; background-image: url("https://picsum.photos/368/268?random=101"); background-size: cover; background-position: center; border-radius: 18px; text-shadow: 0 2px 6px rgba(0, 0, 0, .6); p { color: #fff; font-size: 18px; } } /* 系统深色模式自动换图 */ @media (prefers-color-scheme: dark) { .card { background-image: url("https://picsum.photos/368/268?random=202"); } }
解析
1、prefers-color-scheme是规则级,可以替换整段 CSS。
2、切换系统主题 => 背景图直接变。
3、light-dark()无法做到图片切换,因为它只能返回属性值。
light-dark() 的真实执行模型
light-dark() 到底在做什么?
很多人理解成:"自动切换颜色"
但真实模型是:在"计算值阶段(computed value stage)"做一次条件选择
csscolor: light-dark(black, white);浏览器执行流程:
bash解析 CSS ↓ 计算属性值(computed value) ↓ 根据当前 used color scheme 选择 black 或 white ↓ 得到最终值注意关键词
1、它发生在 值计算阶段
2、不是规则切换
3、不会生成新的 CSS 规则
为什么它不能切图片?
因为 CSS 有严格的类型系统
类型 示例 <color>#fff <image>url(...) <length>10px
light-dark()的签名是:light-dark(<color>, <color>);,它只接受 <color> 类型
所以:background-image: light-dark(url(a), url(b));,类型不匹配 => 直接无效
light-dark() 经典案例
html
html<div class="card"> 颜色会根据系统主题自动切换 </div>
style
css* { margin: 0px; padding: 0px; box-sizing: border-box; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; } :root { /* 必须声明 */ color-scheme: light dark; } body { width: 100vw; height: 100vh; display: grid; place-items: center; background: #00ff00; } .card { width: 368px; height: 268px; padding: 8px; text-align: center; line-height: 268px; /* 值级切换,颜色跟随系统主题 */ background: light-dark(#ffffff, #111111); color: light-dark(#111111, #ffffff); border-radius: 18px; text-shadow: 0 2px 6px rgba(255, 255, 255, .6); transition: .3s; }
prefers-color-scheme 与 light-dark() 的区别
特性 prefers-color-scheme light-dark() 作用层级 规则级(整段 CSS) 值级(单个属性) 可切换类型 图片、布局、颜色、资源路径 颜色、数值、单位 响应触发 系统深浅色切换 系统深浅色切换 是否可点击触发 ❌ ❌ 代码复杂度 中等 极简
一句话理解
颜色 =>light-dark()
资源 / 布局 / 整段规则 =>prefers-color-scheme
这也是现代 CSS 主题系统的标准分工。
color-scheme 才是幕后真正控制者
代码里真正发生的事情
css:root { color-scheme: light dark; --bg: light-dark(#ffffff, #111111); }这里其实做了三件事
1、声明页面支持两种主题
csscolor-scheme: light dark;作用是告诉浏览器,可以使用深色 UI 或可以使用浅色 UI
否则,light-dark()可能不会按预期工作或表单、滚动条不会自动变暗
2、定义"依赖环境"的变量
css--bg: light-dark(#ffffff, #111111);这个变量不是固定值,而是:一个依赖环境的动态计算值
3、变量在"使用时"才被解析
css.card { background: var(--bg); }执行顺序
bash读取 var(--bg) ↓ 展开为 light-dark(...) ↓ 根据当前 color-scheme 选择值 ↓ 得到最终颜色
prefers-color-scheme 、light-dark() 和 color-scheme 三者分别是什么?
bashprefers-color-scheme 是在"规则层"做条件分支 light-dark() 是在"值计算阶段"做条件选择 color-scheme 是决定整个页面"使用哪种主题上下文"的根控制器
主题切换到底在解决什么?
很多人理解为:深色 / 浅色 切换
但工程上真正的问题是:如何在不同"视觉语义"下,统一管理设计变量
关键词:
1、不是"颜色切换"
2、是 Design Token(设计令牌)切换
推荐架构(核心思想)
现代主题系统应该分三层
bash设计语义层(Design Token) 主题环境层(Theme Context) 表现层(Component Style)
1、设计语义层(最关键)
错误写法
csscolor: #111111; background: #ffffff;正确写法
css:root { --color-text: light-dark(#111111, #ffffff); --color-bg: light-dark(#ffffff, #111111); --color-border: light-dark(#e5e7eb, #374151); }这里发生了质变:1、不再关心"黑或白";2、只关心"语义"
主题环境层(prefers-color-scheme 的职责)
这一层只做一件事:决定当前是 light 还是 dark
css@media (prefers-color-scheme: dark) { :root { --elevation-shadow: 0 4px 12px rgba(0,0,0,.6); } }注意:
这里适合处理:
1、阴影
2、图片
3、特殊效果
不适合写大量颜色(否则会失控)
表现层(组件使用)
css.card { background: var(--color-bg); color: var(--color-text); border: 1px solid var(--color-border); }组件层永远不关心:现在是 dark 还是 light
它只关心:我用什么语义变量
为什么必须这样分层?
因为这能解决三个"工程级问题":可维护性、可扩展性(未来不止 dark/light)和跨端统一。
可维护性
css@media (prefers-color-scheme: dark) { .card { ... } .btn { ... } .header { ... } }问题:
1、每个组件都要写一遍
2、主题逻辑分散
可扩展性(未来不止 dark/light)
未来可能有:dark、light、dim、oled或high-contrast
如果用的是:light-dark()直接扩展失败
跨端统一
做的可能是:Web、小程序、App(Flutter / Swift)
Design Token 可以:一套变量 => 多端复用
light-dark() 在架构中的正确定位
很多人误用它。正确理解是:它只是"默认 token 的快速写法"
适合场景
css:root { --color-text: light-dark(#111111, #ffffff); }优点
1、写法简洁
2、无需 media query
不适合场景
cssbackground-image: light-dark(...)
csslayout: light-dark(...)原因
1、它不是规则系统
2、只是值选择器
最终推荐组合(最佳实践)
基础层(color-scheme)
css:root { color-scheme: light dark; }
Token 层(light-dark)
css:root { --color-bg: light-dark(#ffffff, #111111); --color-text: light-dark(#111111, #ffffff); }
规则层(prefers-color-scheme)
css@media (prefers-color-scheme: dark) { :root { --bg-image: url(dark.jpg); } }
使用层(组件)
css.card { background: var(--color-bg); color: var(--color-text); }
最终认知升级(关键一句话)
bashprefers-color-scheme 决定"规则走哪一套" light-dark() 决定"值选哪一个" Design Token 决定"系统如何组织"