应使用 js 监听 matchmedia 的 change 事件并用 setproperty 动态设置 css 变量:先获取 media 对象,定义更新函数写入 --theme-mode 或 --reduce-motion,绑定 change 事件,并立即执行一次确保初始值正确。

怎么用CSS变量接住用户偏好设置
浏览器的 prefers-color-scheme、prefers-reduced-motion 这些媒体查询能感知系统级偏好,但它们只在初始渲染时生效,没法响应用户中途手动切换(比如深色模式开关)。真正动态响应,得靠 JS 主动读取 + CSS 变量注入。
核心思路是:JS 监听 matchMedia 的 change 事件,拿到当前值后,用 document.documentElement.style.setProperty() 写入对应 CSS 变量。
-
prefers-color-scheme返回"light"或"dark",可映射为--theme-mode: dark -
prefers-reduced-motion返回true/false,适合转成--reduce-motion: 1或0 - 别直接覆盖整个
:root样式表——用setProperty动态更新更轻量、可预测
为什么不能只靠@media + CSS变量级联
CSS 媒体查询本身不触发重绘变量值。比如你写了 @media (prefers-color-scheme: dark) { :root { --bg: #1a1a1a; } },这只能在页面加载或媒体状态变更(如系统切深色)时生效;但如果用户点了一个自定义“深色开关”按钮,这个 CSS 规则根本不会重新计算。
本质区别在于:媒体查询是声明式、被动响应;而 JS + setProperty 是命令式、主动控制。
- 媒体查询无法监听 JS 主动触发的偏好变更(比如 localStorage 存的用户选择)
- CSS 变量一旦被 JS 设置,后续所有用到它的
var(--bg)都会立刻更新,无需刷新 - 多个偏好源(系统 + 用户设置 + URL 参数)必须由 JS 统一归口判断优先级,CSS 做不到
监听prefers-color-scheme变更的实际写法
直接监听 matchMedia 对象的 change 事件是最稳妥方式,比轮询或重试更高效。
const media = window.matchMedia('(prefers-color-scheme: dark)');
function updateThemeMode(e) {
document.documentElement.style.setProperty(
'--theme-mode',
e.matches ? 'dark' : 'light'
);
}
media.addEventListener('change', updateThemeMode);
updateThemeMode(media); // 立即执行一次,确保初始值正确
- 必须调用一次
updateThemeMode(media),否则页面刚加载时变量为空 - 不要用
media.matches在组件里反复读取——它不响应变化,只是快照 - 旧版 Safari(addEventListener 到
matchMedia,需降级用media.addListener
用户手动切换时如何避免样式抖动
最常见坑是:JS 更新变量后,CSS 过渡没对齐,导致背景先变、文字后变,或者动画突然卡顿。
关键不是“加 transition”,而是让所有依赖该变量的属性都走同一套缓动逻辑,并且过渡时机可控。
- 把
transition写在根元素上,例如::root { transition: background-color 0.3s ease, color 0.3s ease; } - 避免在多个选择器里重复写
transition,容易冲突或遗漏 - 如果用了
prefers-reduced-motion,记得在 JS 中同步关掉transition:用document.documentElement.style.setProperty('--can-animate', canAnimate ? '1' : '0'),再在 CSS 中用transition: ... calc(var(--can-animate) * 0.3s)
变量和 JS 的耦合点其实很薄,但优先级、初始同步、浏览器兼容性这几个地方,稍不注意就会让“动态响应”变成“偶发失效”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











