纯前端实现主题切换:CSS 变量、系统偏好与首屏防闪烁

举报
yd_213843956 发表于 2026/10/30 07:58:54 2026/10/30
【摘要】 深色模式不只是换一组颜色。这篇讲清楚 CSS 变量分层、system/light/dark 三态切换、首屏防闪烁和跨标签页同步。

主题切换看起来只是把页面颜色换掉,真正上线后常见的问题却不少:刷新时先闪一下白屏、系统主题变化后页面不跟随、两个标签页的设置互相打架。这篇把实现链路拆开。

一、先建立颜色令牌

不要在组件里散落颜色值。先把背景、文字、边框和强调色定义成 CSS 变量,用 data-theme 在根节点切换:

:root {
  --bg: #fff;
  --text: #1f2329;
  --line: #e5e6eb;
}
:root[data-theme="dark"] {
  --bg: #17181c;
  --text: #e8eaed;
  --line: #34363c;
}
body { background: var(--bg); color: var(--text); }

组件只消费语义变量,主题切换就变成根节点属性的一次变化。

二、区分系统、浅色和深色三种状态

设置项不要只存 light/dark,还要保留 system。system 时用 matchMedia 判断当前偏好,并监听变化:

const media = matchMedia('(prefers-color-scheme: dark)');
function resolve(mode) {
  return mode === 'system' ? (media.matches ? 'dark' : 'light') : mode;
}
media.addEventListener('change', () => {
  if (localStorage.getItem('theme') === 'system') apply(resolve('system'));
});

三、首屏防闪烁

如果等应用脚本加载再设置主题,浏览器已经按默认色画过一帧,就会闪白。把一小段同步脚本放在 head 里,在样式绘制前写入 data-theme:

<script>
  const mode = localStorage.getItem('theme') || 'system';
  const dark = matchMedia('(prefers-color-scheme: dark)').matches;
  document.documentElement.dataset.theme =
    mode === 'system' ? (dark ? 'dark' : 'light') : mode;
</script>

这段脚本只读本地设置和系统偏好,不依赖网络,放在 head 中足够早。

四、跨标签页同步

用户在另一个标签页切换主题时,监听 storage 事件即可同步。storage 事件不会在触发写入的当前页面重复执行,正好避免循环更新。

addEventListener('storage', (e) => {
  if (e.key === 'theme') apply(resolve(e.newValue || 'system'));
});

五、过渡与可访问性

切换时给背景和文字加短过渡会更顺,但要尊重减少动画偏好。用 prefers-reduced-motion 关闭过渡,并保证切换按钮有明确的可访问名称和当前状态。

六、小结

稳的主题切换由四部分组成:语义颜色变量、system/light/dark 三态、head 内同步初始化防闪烁、storage 跨页同步。把状态和颜色令牌分开管理,后续增加品牌主题也更容易。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。