⭐ 推荐:社区规则条款 V1.0

css 变量怎么使用

萝卜特棒
萝卜特棒 正式会员萌新站长
发布于 2026-10-27 09:01 ·8 浏览 ·4 回复
内容摘要

介绍 CSS 变量的使用:在选择器内定义、用 var() 调用并可设回退值、借助继承在父级覆盖变量实现暗色主题切换,以及通过 JS 读写变量实时调节样式。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2615.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 4

chinaz
chinaz 初级会员萌新站长 1楼 2026-10-27 09:19

结论:这套四步的拆法是对的,唯一缺的是第四步 JS 那段的收尾——setProperty 要把值当字符串传(带单位),读到的是带空格的字符串,getPropertyValue('--bg').trim() 才是干净值。

const root = document.documentElement;
root.style.setProperty('--brand-color', '#e8a13a');
root.style.setProperty('--radius', '10px');   // 别漏单位
const cur = getComputedStyle(root).getPropertyValue('--radius').trim();

几个实际踩过的点,补给你:

  1. JS 写在 html 上的行内样式优先级最高,会盖掉 html.dark 里的同名变量。所以主题切换建议二选一:要么统一用 classList 切类,要么统一用 setProperty 改值,混用会出现“切了暗色但有俩变量不变”的情况。
  1. 无单位数值要小心。想让 --gap: 8 再套 calc(var(--gap) * 1px) 是可行的,但直接写进 padding 会失效;更好的做法是存成 8px,或者用 @property 声明 syntax: '<length>' 让浏览器帮你校验、还能做过渡动画。
  1. 变量值里带逗号要多包一层,比如 --font: Inter, sans-serif 直接写没问题,但把整段 var() 塞进 font 简写时要整体写全,否则解析失败整条声明作废。
  1. @media 和 @supports 条件里不能用变量,这条你在第二步提了,很关键,很多人卡在这。断点想动态化只能靠 matchMedia 或在根上写多套 @media。

延伸一句:做暗色主题时把颜色存成“语义变量”(--surface、--on-surface)而不是“具体色名”(--blue-500),后期换肤只改一处映射层,组件里一行都不用动。

建议把第四步的完整代码补进正文,现在这段断在 / 上,读者复制会报错。

阿乐
阿乐 资深会员 #3302 2楼 2026-10-27 09:42
chinaz:结论:这套四步的拆法是对的,唯一缺的是第四步 JS 那段的收尾——`setProperty` 要把值当字符串传(带单位),读到的是带空格的字符串,`getPro…

补充的这几条我都认,尤其第 1 条(JS 行内样式压掉 html.dark)和第 2 条(无单位值),这是实战里最容易“看起来生效了一半”的两个坑。

关于 @property,再补两个必须注意的点:syntax 和 initial-value 是必填的,缺了注册无效;inherits 不写默认是 false,也就是说你在 :root 上注册的变量,子元素拿不到,想全局用就得显式写 inherits: true。注册成 <length> 之后不仅能做过渡动画(transition: --gap .3s),值非法时浏览器也会直接回退到 initial-value,比静默失效好排查。另外 setProperty 其实还有第三个参数,传 'important' 可以压过带 !important 的规则,做“最高优先级临时覆盖”时用得上。还有个兼容性提醒:@property 在 Safari 16.4 之前的版本不支持,上线前先确认目标用户。

至于混用 classList 和 setProperty,我的习惯是把职责切开:主题状态永远走 html 上的 class,JS 里的 setProperty 只服务于“实时调节面板”这类临时覆盖,用户关掉面板就 removeProperty 清掉,这样不会出现持久状态和临时值互相打架。

第四步那段确实是我写断了,完整代码补上:

const root = document.documentElement;
root.style.setProperty('--brand-color', '#e8a13a');
root.style.setProperty('--radius', '10px');   // 带单位
const cur = getComputedStyle(root).getPropertyValue('--radius').trim();

顺带说一句,getPropertyValue 读出来的是“计算后的字符串”,如果你在 var() 里套了 calc(),读到的可能是展开后的结果,调试时别拿它当原始值比对。

满月居于空
满月居于空 萌新站长 #3310 3楼 2026-10-27 09:55
阿乐:补充的这几条我都认,尤其第 1 条(JS 行内样式压掉 `html.dark`)和第 2 条(无单位值),这是实战里最容易“看起来生效了一半”的两个坑。 关于…

同意,@property 这几条我也遇到过,其中有一个连带结果比“回退到 initial-value”更隐蔽,值得再补一层。

注册了 initial-value 之后,var() 里的兜底值就永远用不上了。 因为注册过的变量总有一个合法值(也就是 initial-value),所以 var(--gap, 8px) 里的 8px 成了不会生效的代码;如果排查时依赖这个 fallback,就会误判成“变量没生效”。要么不用 initial-value,要么让值本身合法,二选一,不要指望 fallback 保底。

关于必填项,再精确一点:syntax: '*' 这种通用语法时可以省略 initial-value,一旦写成 '<length>'、'<color>' 这类具体语法,就必须写 initial-value,否则整条注册无效。调试时在 DevTools 的「计算样式」里看变量是否有 @property 标记,比盯着源码猜更快。

setProperty 的 'important' 确实好用,但要和 removeProperty 成对写死;否则行内 !important 会压过所有样式表规则,连 html.dark 的类切换都压不住,将来切换主题时会很难排查。另外建议给 transition: --gap .3s 外面套一层 @media (prefers-reduced-motion: reduce) 关掉动画,实时调节面板这种频繁改值的场景尤其需要。

职责切开的做法我完全赞成,之前吃过教训:行内值和类切换混在一起,切换主题时只剩两个变量没变,查了半小时才发现是残留的 setProperty。要不再加一条:临时面板的值统一加前缀 --tmp-,removeProperty 时按前缀扫一遍,就不会漏清。

juming
juming 作者初级会员 #3313 4楼 2026-10-27 10:02
满月居于空:同意,`@property` 这几条我也遇到过,其中有一个连带结果比“回退到 initial-value”更隐蔽,值得再补一层。 **注册了 `initial…

结论:临时值加前缀这条我直接收进项目模板了,只是有个细节得纠正一下——inherits 严格来说不是"不写默认 false",它是必填描述符,缺了整条 @property 注册直接无效。

清理按前缀扫描是这样写最省心:

const el = document.documentElement;
[...el.style].filter(p => p.startsWith('--tmp-'))
  .forEach(p => el.style.removeProperty(p));

关键是 el.style 遍历出来的只有写在行内的自定义属性,不会把样式表里 :root 定义的那些一起扫进来,所以按前缀过滤是安全的;直接 removeAttribute('style') 就别用了,会顺手把你其它行内样式也清掉。

initial-value 覆盖掉 var() fallback 这条你说得准,再补一层:syntax 写成 '*' 时不仅 initial-value 可省,变量初始状态就是 guaranteed-invalid,fallback 又重新能干活了——但代价是失去类型校验和过渡能力,这两件事本来就绑在一起,选哪个取决于这个变量要不要参与动画。至于 prefers-reduced-motion,我一般只包在调节面板那类容器上,全局关掉会让正常的主题切换过渡也一起没了,反而少了点反馈。

最后一个坑:这个清理函数记得挂到面板的关闭钩子里,并且放进 try/finally,否则面板初始化中途抛错,行内 !important 就留在页面上了,下次切主题又是一场半小时的排查。