惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Attack and Defense Labs
Attack and Defense Labs
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Hacker News
The Hacker News
C
Cyber Attacks, Cyber Crime and Cyber Security
A
Arctic Wolf
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Troy Hunt's Blog
小众软件
小众软件
L
LangChain Blog
Schneier on Security
Schneier on Security
D
DataBreaches.Net
爱范儿
爱范儿
P
Proofpoint News Feed
博客园 - 聂微东
M
MIT News - Artificial intelligence
Hacker News: Ask HN
Hacker News: Ask HN
T
Tailwind CSS Blog
Vercel News
Vercel News
V
Visual Studio Blog
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
Jina AI
Jina AI
阮一峰的网络日志
阮一峰的网络日志
博客园 - 【当耐特】
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
T
Threat Research - Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
月光博客
月光博客
Spread Privacy
Spread Privacy
C
Cisco Blogs
S
Securelist
量子位
S
Security @ Cisco Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Tenable Blog
WordPress大学
WordPress大学
T
The Exploit Database - CXSecurity.com
宝玉的分享
宝玉的分享
C
Cybersecurity and Infrastructure Security Agency CISA
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
MongoDB | Blog
MongoDB | Blog
C
CERT Recently Published Vulnerability Notes
Help Net Security
Help Net Security
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 司徒正美
B
Blog RSS Feed
博客园_首页
Security Archives - TechRepublic
Security Archives - TechRepublic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org

纸鹿摸鱼处

北京实习300天 | 纸鹿摸鱼处 实习后买了一堆东西 | 纸鹿摸鱼处 谈谈不受欢迎的博客技术特征 | 纸鹿摸鱼处 我们的设备被拿来做了什么:软件的背景行为 | 纸鹿摸鱼处 生成式AI正在生成 | 纸鹿摸鱼处 前端字体二三事 | 纸鹿摸鱼处 寻不回手工油糕 | 纸鹿摸鱼处 一些数码外设的评价和“捡垃圾”心得 | 纸鹿摸鱼处 朋友的 Vivado 和 Modelsim 装上了吗 | 纸鹿摸鱼处 迟到三月的年末博客杂谈:动力、文章和内容 | 纸鹿摸鱼处 VitePress 不完全优化指南 | 纸鹿摸鱼处 给电脑新手的笔记本开荒指南导读 | 纸鹿摸鱼处 再谈考试时钟:技术之外的思考 | 纸鹿摸鱼处 今日校园见闻:我已经出离高兴了 | 纸鹿摸鱼处 配置并差点运行不了 Java Applet | 纸鹿摸鱼处 Windows 上的开源软件入门 | 纸鹿摸鱼处 博客进化:从 Hexo 到 Nuxt Content | 纸鹿摸鱼处 祖母的90年代南昌差旅之险 | 纸鹿摸鱼处 Linux 下通过 Samba 共享文件夹 | 纸鹿摸鱼处 从“端口无法访问”到本地环境问题——朋友的博客踩坑记录 | 纸鹿摸鱼处 Windows 空间管理/清理指南 | 纸鹿摸鱼处 遭遇APP乱象:老人的「钱袋子」开线了 | 纸鹿摸鱼处 封面被 Linux QQ 官方用了,但是有一些小缺陷 | 纸鹿摸鱼处 Windows 终端体验优化指南 | 纸鹿摸鱼处 有问题,就要讲出来。 | 纸鹿摸鱼处 从 CSS 排序折腾到 Stylelint 和 pnpm | 纸鹿摸鱼处 使用 DOSBox 的多种方式 | 纸鹿摸鱼处 今日有「假」 | 纸鹿摸鱼处 把博客文章按年份放在子文件夹中了 | 纸鹿摸鱼处 写过的奇妙代码。 | 纸鹿摸鱼处 Arch Linux 启动引导修复 | 纸鹿摸鱼处 在延长今天和期待明天之间 | 纸鹿摸鱼处 今日无事,除了一些技术咨询 | 纸鹿摸鱼处 系统引导配置分享 | 纸鹿摸鱼处 病毒高占用CPU,三天杀俩 | 纸鹿摸鱼处 运营商又想多收话费了 | 纸鹿摸鱼处 电脑连接手机,手机给电脑传文件…… | 纸鹿摸鱼处 Moonlight 串流指南 | 纸鹿摸鱼处 SSH 免密登录 | 纸鹿摸鱼处 Termux 简单指南 | 纸鹿摸鱼处 Tailscale 不完全使用指南 | 纸鹿摸鱼处 Windows 安装指南 | 纸鹿摸鱼处 Arch Linux 易用性及美化 | 纸鹿摸鱼处 Arch Linux 初步配置 | 纸鹿摸鱼处 使用 ArchInstall 安装 Arch Linux | 纸鹿摸鱼处 VS Code 简单配置项 | 纸鹿摸鱼处 他修电脑又遇到了我 | 纸鹿摸鱼处 修监控 (授权转载) | 纸鹿摸鱼处 被大块落冰砸中的话—— | 纸鹿摸鱼处
深色模式开发的最佳实践 | 纸鹿摸鱼处
纸鹿本鹿, hi@zhilu.cyou · 2025-04-14 · via 纸鹿摸鱼处

前端深色模式不止于 prefers-color-scheme,完整解析自动检测、手动切换的实现方案,解决闪屏、样式冲突、变量管理等常见痛点,提供稳健的深色模式最佳实践。

自动深色模式

使用 CSS 媒体查询

相信大家在查阅深色模式实现时,都遇到过这种写法:

css
@media (prefers-color-scheme: dark) {
	body {
		background-color: black;
		color: white;
	}
}

渐进增强而非并列查询

有些同学会说,诶,我有个好主意,我了解 prefers-color-scheme 的值还有 lightno-preference,我把基础样式并列地写进这三种媒体查询里。

这对吗?Chrome 76 起才支持 prefers-color-scheme 媒体特性,放在某些兼容性差的浏览器里,这些规则都不会生效。

所以最好采用「渐进增强」的策略,即先写基础样式,再写深色模式样式,这样可以保证低版本内核的浏览器正常显示。

css
body {
	background-color: white;
	color: black;
}

@media (prefers-color-scheme: dark) {
	body {
		background-color: black;
		color: white;
	}
}

使用 CSS 变量

又有同学要头疼了,按钮、弱文本也要适应深色模式,那我岂不得维护许多深色模式下的 CSS 声明块?

这个时候,CSS 变量 就派上用场了。我们可以维护一个普通模式和深色模式的「调色盘」,其他声明块中需要使用颜色,只需要通过 var(--my-color-1) 的形式引用即可。

https://cooo.site/asset/index.csscss
:root {
	--bg1: #f7f9ff;
	--bg2: #fff;
	--text1: #334;
	--text2: #556;
	--text3: #aab;
	--line: hsl(240 33% 60% / 0.2);
	--ac1: #37f;
	--ac2: hsl(210 100% 50% / 0.5);
	--ac3: hsl(210 100% 50% / 0.2);
}

@media (prefers-color-scheme: dark) {
	:root {
		--bg1: #111;
		--bg2: #222;
		--text1: #eee;
		--text2: #ccc;
		--text3: #777;
		--ac1: #fc2;
	--ac2: hsl(45 100% 50% / 0.5);
	--ac3: hsl(45 100% 50% / 0.2);
	}
}

另外,要注意各个颜色模式下的对比度,在确保文字清晰的同时,不要使用过高的对比度(纯黑、纯白),让用户的体验更舒适。收起 「A屏黑」 的想法,纯黑色省电的代价是黑暗环境中高对比度对用户视觉的刺激。

手动深色模式

又有同学在想了:我们做出的自动深色模式只是设备支持下的自动切换小彩蛋,如何显式展示技术力,比如放个深色模式按钮呢?

假设我们已经通过 localStorage 存储了用户主题并在加载时给 <html> 添加了 data-theme 属性,看起来完美实现了颜色模式切换,其实还会遇到几个降低用户体验的问题。

浏览器生成的自动深色模式

你的浅色模式在设备的浅色模式下表现很好,但是当设备在深色模式时,你的浅色模式可能表现得一团糊涂。

怎会如此!?Chrome 的贴心设计优化了不支持深色模式的网页显示,但成为了开发者在深色模式下展示浅色网页的心智负担。

我们需要通过 color-scheme 告诉浏览器网页支持深色和浅色模式,不要让浏览器扳倒我们自己的适配。两种方法二选一即可:

  • 在 HTML 头部通过元数据声明 <meta name="color-scheme" content="light dark">
  • 在 CSS 中声明 :root { color-scheme: light dark; }

记得在手动深色模式(类名而非媒体查询的实现)下设置 [data-theme="dark"] { color-scheme: dark; }。不然想一想 color-scheme: light dark 会怎么表现?滚动条、按钮等元素在 用户代理样式表 给出的是浅色的样式!这和手动深色模式的预期不一致。

页面初载时的闪烁

你的深色模式在设备深色模式下表现很好,但是当设备在浅色模式下,你的深色模式在初载时可能闪白,这对于用户眼睛的伤害是巨大的(点名批评 GitHub Docs)。

我曾使用 Pinia 管理用户主题,但 Pinia 的挂载需要时间,产生了页面闪白问题。我留意到一些网站是没有的,比如 VitePress,我研究了其代码实现(vitepress/src/node/config.ts#L360)。它在 <head> 中添加了同步的 <script>,通过 IIFE 立即执行函数,在 DOM 构建完成前就完成了主题切换,避免了页面闪白。

html
<head>
	<script>
		if (localStorage.getItem('theme') === 'dark')
			document.documentElement.dataset.theme = 'dark'
	</script>
</head>

一致性和 CSS 代码冗余

代码冗余会轻松地创建不一致,而不一致带来潜在的破坏力是惊人的:

css
:root {
	--c-bg: white;
	--c-text: black;
}

@media (prefers-color-scheme: dark) {
	/* 原本是 :root[data-theme="auto"]
	 * 但下一个选择器应该有相同或更高的优先级 */
	:root {
		--c-bg: #111;
		--c-text: #eee;
	}
}

[data-theme="dark"] {
	--c-bg: black;
	--c-text: white;
	color-scheme: dark;
}

这样,你的深色模式定义了两次,你需要小心地维护这些代码。也许可以用 @mixin 来减少冗余?但终归不是完美的解决方案。

这时,抛弃掉 [data-theme="auto"] 就显得有用了——通过在 JS 中提前让「自动」的状态「塌缩」为确定的「浅色」或「深色」,CSS 中只需维护两种模式下的对应样式即可。

不要忘记监听 prefers-color-scheme 的变化:

ts
const colorSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)')

function setTheme(theme?: 'light' | 'dark' | 'auto') {
	if (theme)
		localStorage.setItem('theme', theme)

	const targetTheme = theme || localStorage.getItem('theme') || 'auto'
	const isDark = targetTheme === 'dark' || (targetTheme === 'auto' && colorSchemeQuery.matches)

	document.documentElement.dataset.theme = isDark ? 'dark' : 'light'
	// 如果使用类名实现,则
	// document.body.classList.toggle('dark', isDark ? 'dark' : 'light')
}

colorSchemeQuery.addEventListener('change', () => setTheme());

setTheme()

这里的代码只是给出实现思路,具体实现一个完美的深色模式还要多费些心思。什么,竟然有人操作 document.getElementsByTagName('html')[0].className?回家吧孩子。

Take care of users, and code well. ☝️