应优先使用 CSS 自定义属性配合类名切换主题,而非动态修改 link 的 href,以避免 FOUC、支持平滑过渡、便于维护和兼容系统偏好。

直接修改 <link> 的 href 会触发 FOUC
用 JavaScript 动态替换 <link rel="stylesheet"> 的 href 是最直觉的做法,但实际会导致页面短暂白屏或样式错乱(FOUC)。浏览器必须丢弃旧样式表、发起新请求、等待下载解析,期间 DOM 渲染无样式支撑。
- 即使 CSS 文件已缓存,仍需重新解析并重建 CSSOM,性能开销不可忽略
- 无法平滑过渡:
transition在样式表切换瞬间失效,颜色/背景等变化是“跳变”而非渐变 - 多主题数量增加时,打包体积和 HTTP 请求数线性增长,不利于长期维护
document.documentElement.classList.toggle() 是更稳的选择
把所有主题规则写进同一份 CSS,通过给 添加不同类名(如 theme-dark、theme-high-contrast)来激活对应样式块。JS 只负责切换类,不触碰样式表加载流程。
- CSS 中用属性选择器写法:
.theme-dark body { background: #121212; color: #e0e0e0; } - 切换逻辑干净:
document.documentElement.classList.toggle('theme-dark') - 配合
localStorage持久化:localStorage.setItem('ui-theme', 'theme-dark'),页面加载时读取并classList.add() - 支持嵌套主题,比如
.theme-dark .card和.theme-dark.theme-compact .card可叠加生效
用 setProperty() 更新 CSS 变量才是真动态
如果主题不只是明暗二分,而是允许用户自定义主色、字号、圆角等,硬编码类名会爆炸式膨胀。此时应把可变部分抽成 CSS 自定义属性,在 JS 中仅更新变量值。
- 基础声明写在
:root::root { --primary: #3498db; --bg: #fff; } - 深色主题覆盖:
.theme-dark { --primary: #1abc9c; --bg: #2c3e50; } - JS 修改单个值:
document.documentElement.style.setProperty('--primary', '#9b59b6') - 注意:变量值变更后,所有使用该变量的元素会自动重绘,无需手动触发,但动画需显式加
transition: background-color 0.2s等声明
别忽略系统偏好与降级兼容
用户可能已开启系统级暗色模式,或使用低配设备。JS 主题切换逻辑不能只依赖按钮点击。
立即学习“前端免费学习笔记(深入)”;
- 监听系统变化:
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {...}) - 初始化时优先读
localStorage,其次 fallback 到matchMedia结果,最后才用默认主题 - CSS 变量在 IE 中完全不支持,若需兼容,得用类名方案兜底,并在 JS 中检测
CSS.supports('color', 'var(--x)') - 服务端渲染(SSR)项目中,首次 HTML 必须带正确类名或
style属性,否则首屏仍是亮色再闪变
theme.js 文件里,反而会让下次改色的人崩溃。


















