纯前端实现主题切换:CSS 变量、系统偏好与首屏防闪烁
【摘要】 深色模式不只是换一组颜色。这篇讲清楚 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)