应使用 document.documentElement.setAttribute('data-theme', 'dark') 切换主题,避免直接改 className;变量须在 :root 修改并显式声明样式,过渡属性需基础选择器中定义且避免深层嵌套匹配。

直接改 document.documentElement.className 触发全量 CSSOM 重建
很多 H5 页面用 className = 'theme-dark' 这种方式切换主题,看着简单,但每次赋值都会让浏览器丢弃旧的 class 缓存,重新解析全部 CSS 规则、重建 CSSOM、再重排重绘。尤其在低端 Android 设备上,CSS 文件超 150KB 或变量超过 30 个时,卡顿肉眼可见。
更麻烦的是,如果页面里有统计 SDK、埋点脚本或框架(比如 Vue 的 SSR hydration)也在读写 className,就可能相互覆盖——你加了 theme-dark,它又给你清掉,主题就失效了。
- ✅ 正确做法:用
classList.add('theme-dark')和classList.remove('theme-light')增量操作 - ✅ 更推荐:别用 class 控制主题,改用
data-theme属性,比如document.documentElement.setAttribute('data-theme', 'dark') - ❌ 避免混写:不要把主题类和功能类(如
is-loaded、has-animation)塞进同一个className
data-theme 切换后样式没动,其实是选择器写深了
很多人写了 [data-theme="dark"] .container :root 这类嵌套选择器,结果发现变量更新了,但颜色就是不生效。问题出在匹配开销:浏览器得对每个 :root 节点都做属性判断,还得多层嵌套回溯,低端机直接卡住。
- ✅ 只写一层:用
[data-theme="dark"] :root,别加中间选择器 - ✅ 合并变量:把
--border-color和--divider-color这类语义相近的变量合并,减少匹配次数 - ❌ 不要带空格:像
--bg color是无效变量名,CSS 解析直接忽略
style.setProperty() 改了变量却读不到新值
常见错误是写成 document.body.style.setProperty('--bg-color', '#000')——这完全无效。因为 --bg-color 必须在 :root 作用域下定义和修改,body.style 只影响 body 自身及其继承链,无法触达全局变量。
立即学习“前端免费学习笔记(深入)”;
另一个坑是 JS 修改后立刻调用 getComputedStyle(document.documentElement).getPropertyValue('--bg-color'),经常拿到旧值。这不是 bug,而是变量更新异步应用,不是同步反射。
- ✅ 必须用
document.documentElement.style.setProperty('--bg-color', '#000') - ✅ 如果需要读取新值,得等一帧:用
requestAnimationFrame(() => { /* 读取 */ }) - ✅ 所有过渡属性(
color、background-color、border-color)必须显式声明在元素上,不能只靠继承
过渡动画“闪一下”或部分元素不动
即使加了 transition: all 300ms,仍可能看到按钮变色了、文字没动、卡片背景闪一下——根本原因不是 transition 写错了,而是某些元素压根没声明目标属性,或者声明在错误的选择器分支里。
- ✅ 每个用到变量的地方,都要显式写出属性:比如
color: var(--text-color),哪怕只是重复一遍 - ✅ transition 规则必须写在基础选择器中(如
body),不能只写在[data-theme="dark"] body里 - ✅ 推荐缓动函数:
cubic-bezier(0.25, 0.46, 0.45, 0.94),比ease更顺滑;时间控制在 300–400ms,太短像卡住,太长像延迟
真正卡顿的点往往不在 JS 执行,而在 CSSOM 匹配深度、变量作用域错位、以及 transition 规则被媒体查询或条件选择器意外覆盖——这些细节不查 DevTools 的 Rendering 面板很难定位。


















