CSS自定义属性在媒体查询中无法直接定义并全局生效,根本原因是其作用域受限于声明位置,且媒体查询不提升变量可见性;必须先在:root中声明默认值,再在媒体查询中同选择器覆盖。

CSS 自定义属性(--my-color)不能在媒体查询中“直接定义”并期望它全局生效,根本原因不是语法错误,而是 CSS 变量的作用域机制决定的:它们只在声明所在的 CSS 作用域内有效,媒体查询本身不创建新的全局作用域,也不提升变量可见性。
自定义属性在媒体查询中声明后为何“找不到”
当你写:
@media (max-width: 768px) {
:root {
--primary: red;
}
}
这看似定义了变量,但实际效果是:只有在该媒体查询匹配时,:root 才会应用这个声明;而其他未匹配断点的环境里,--primary 根本未被声明——浏览器不会“预设”或“回退”到某个值。更关键的是,如果页面初始加载时断点不匹配(比如桌面端打开),--primary 就是 undefined,后续即使窗口缩放触发媒体查询,已计算的样式(如 color: var(--primary))也不会自动重算,除非你显式触发重绘(如 class 切换、强制 reflow)。
- 变量不是 JavaScript 的 let/const,没有“提升”或“动态绑定”语义
-
var(--primary)在解析时若找不到定义,直接 fallback(或渲染为 inherit/initial),不会等待媒体查询激活 - 媒体查询内的
:root声明仅影响该查询块内后续规则,不覆盖外部已解析的计算值
为什么 :root 外部声明 + 媒体查询内修改能工作
可行写法是先在顶层 :root 中声明默认值,再在媒体查询中覆盖:
立即学习“前端免费学习笔记(深入)”;
:root {
--primary: #007bff;
}
@media (max-width: 768px) {
:root {
--primary: red;
}
}
这样做的本质是利用了 CSS 层叠规则:两个同优先级的 :root 声明,后出现者胜出。只要媒体查询匹配,浏览器就会用新值重新计算所有依赖 var(--primary) 的属性。
- 必须用
:root(而非html或其他选择器),确保作用域覆盖全局 - 不能省略顶层声明,否则小屏下变量未定义,大屏下也无 fallback
- 媒体查询中的修改必须与顶层声明同选择器、同特异性,否则可能被忽略
常见误用:在元素选择器里定义变量并指望媒体查询继承
例如:
.card {
--bg: white;
}
@media (prefers-color-scheme: dark) {
.card {
--bg: #1a1a1a;
}
}
.card {
background-color: var(--bg);
}
这段代码在部分浏览器中表现不稳定,因为 .card 元素的变量作用域仅限于该元素及其后代,且媒体查询是否生效取决于用户系统设置——但更重要的是,如果页面首次渲染时主题是 light,--bg 已按 light 值计算完毕;切换到 dark 模式时,某些浏览器(尤其旧版 Safari)不会自动重算已挂载元素的 var(),导致背景不变。
- 避免在非
:root选择器中定义需响应式切换的变量 - 如必须局部化,改用 class 切换(如
.card.dark)配合独立变量声明 - 对
prefers-color-scheme等动态媒体特性,建议搭配matchMedia()+ JS 监听,主动添加 class
替代方案:用 matchMedia() 动态控制 class 更可靠
当媒体查询逻辑复杂(如多条件组合、需要精确时机控制),纯 CSS 变量易失控。直接用 JS 绑定更可控:
const mql = window.matchMedia('(max-width: 768px)');
document.documentElement.classList.toggle('mobile', mql.matches);
mql.addEventListener('change', e => {
document.documentElement.classList.toggle('mobile', e.matches);
});
然后在 CSS 中写:
:root {
--primary: #007bff;
}
:root.mobile {
--primary: red;
}
这种方式绕过了 CSS 变量在媒体查询中的作用域陷阱,把“何时生效”的决策权交给 JS,同时保持样式逻辑仍由 CSS 承担。
- class 切换会强制浏览器重算所有相关样式,无缓存遗漏风险
- 可与其他状态(如用户偏好、加载状态)组合,灵活性远超纯媒体查询
- 调试时直接查看
<html class="mobile">即知当前状态,比检查媒体查询匹配更直观
真正容易被忽略的点在于:CSS 自定义属性的“响应式”不是靠媒体查询“激活变量”,而是靠媒体查询“覆盖已有变量声明”。一旦漏掉顶层定义,或选错作用域,变量就彻底不可见——这不是浏览器 Bug,是 CSS 层叠模型的必然结果。


















