← 返回文章列表 专栏:前端与建站工程

更新于

技术博客 UI 配色与主题设计:60-30-10 色彩令牌与明暗模式无闪烁切换

基于 CSS 自定义属性构建明暗双色主题,采用 60-30-10 实色层级提升正文对比度,并通过内联脚本消除首屏暗色切换白闪。

出版物风格视觉系统、60-30-10 色彩配比与明暗主题语义令牌
出版物风格视觉系统、60-30-10 色彩配比与明暗主题语义令牌

技术文章通常包含大段正文、代码块与命令输出,单次阅读停留时间较长。如果页面背景纯白刺眼、卡片叠加多层半透明毛玻璃,不仅容易造成视觉疲劳,还会在高分屏滚动时带来不必要的合成层重绘开销。

本文记录如何用 CSS 语义变量组织 60-30-10 实色层级,校核 WCAG 文本对比度,并通过 <head> 顶部同步脚本实现无闪烁的明暗主题切换。

界面信息密度与视觉层级控制

为了让长文阅读界面保持清爽耐看,在样式设计上主要做了四点收敛:

  1. 元信息采用纯文本分隔: 文章发布日期、所属分类、阅读时长与字数统计不包裹圆角背景框,统一使用无框文本并通过中点 · 连接(如 2026.09.14 · 前端与建站工程 · 约 6 分钟),同时为数字开启 font-variant-numeric: tabular-nums 等宽对齐,使列表页与归档页的日期列保持整齐。
  2. 精简标题区装饰元素: 去掉标题上方无实质信息的英文口号或伪终端前缀,直接用中文标题与摘要交代文章主题。
  3. 收拢站点统计信息: 不在首页或归档页顶部堆叠大号数字统计卡片,仅用一行简短说明交代当前收录的文章与项目数量。
  4. 控制表面层级数量: 整屏主要保留两层实色表面:页面底板(--bg-page)与内容卡片(--bg-card),辅以代码块和提示框使用的次级底色(--bg-subtle)。仅在顶部导航栏使用单层磨砂(backdrop-filter: blur(12px)),正文卡片均采用实色背景。

60-30-10 明暗双色设计变量(Design Tokens)

为了兼顾日间阅读与夜间暗光环境,全局样式表按 60% 页面底板、30% 实色卡片与细边框、10% 强调色的比例定义语义化 CSS 变量:

明暗双色纸面层级、设计令牌与无障碍对比度回退结构图
分离 60% 页面底板(--bg-page)、30% 实色阅读卡片(--bg-card)与 10% 语义强调色(--accent),各文字层级均针对实色背景核验 WCAG AA/AAA 对比度。

以下是站点使用的明暗双色 CSS 变量配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* 全局语义化颜色变量(支持 Light / Dark 双模式切换) */
:root,
html[data-scheme="light"] {
--bg-page: #f4f6f9; /* 60% 冷灰纸面底板,减轻纯白 #fff 长时间阅读眩光 */
--bg-card: #ffffff; /* 30% 实色阅读卡片表面 */
--bg-subtle: #edf1f7; /* 行内代码、表头与次级分组底色 */
--bg-code: #0f172a; /* 深色多行代码块背景 */
--text-main: #0f172a; /* 正文主字色(对比度 15.8 : 1,AAA 级) */
--text-secondary: #334155; /* 摘要与导语字色(对比度 9.4 : 1,AAA 级) */
--text-muted: #64748b; /* 日期与辅助元信息(对比度 4.7 : 1,AA 级) */
--accent: #0d9488; /* 10% 青绿强调色,用于链接、选中态与焦点环 */
--accent-soft: rgba(13, 148, 136, 0.1);
--border-color: rgba(15, 23, 42, 0.08);
--shadow-card: 0 1px 3px rgba(15, 23, 42, 0.04), 0 6px 16px -8px rgba(15, 23, 42, 0.05);
}

html[data-scheme="dark"] {
--bg-page: #0b0f17; /* 深石板底色(不用纯黑 #000,减轻高对比度光晕) */
--bg-card: #111827; /* 深灰蓝卡片表面 */
--bg-subtle: #1e293b; /* 深色下的次级表面 */
--bg-code: #070a10; /* 深色下的代码块底色 */
--text-main: #f1f5f9; /* 柔和浅灰白正文(对比度 14.6 : 1) */
--text-secondary: #cbd5e1; /* 次级正文(对比度 10.2 : 1) */
--text-muted: #94a3b8; /* 元信息(对比度 5.6 : 1) */
--accent: #2dd4bf; /* 深色下适度提亮、降饱和度的青绿强调色 */
--accent-soft: rgba(45, 212, 191, 0.14);
--border-color: rgba(248, 250, 252, 0.08);
--shadow-card: 0 2px 6px rgba(0, 0, 0, 0.28), 0 12px 24px -10px rgba(0, 0, 0, 0.35);
}

为什么深色模式不用纯黑(#000000)配纯白(#ffffff)

在 OLED 屏幕上,纯黑背景搭配纯白文字会产生 21:1 的极高对比度,暗光环境下阅读多行文本时容易产生光晕重影(Halation)。使用带冷蓝倾向的深石板色(#0b0f17 / #111827)搭配浅灰白文字(#f1f5f9),将对比度控制在 14.5:1 左右,长时间阅读更为舒适。

首屏无闪烁(Zero-FOUC)的主题同步脚本

如果把读取深浅色主题的逻辑放在页面底部的 DOMContentLoaded 事件或外部 JS 文件中,深色模式用户在刷新页面或点击文章链接时,屏幕会先闪过白底,随后才切回深色(即 FOUC,Flash of Unstyled Content)。

解决办法是在全局布局 <head> 内部靠前位置放入一段同步内联脚本,在浏览器首次绘制(First Paint)前完成 localStorage 读取与系统 prefers-color-scheme 回退:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 置于全局 HTML 布局 <head> 内部最前方 -->
<script is:inline>
(() => {
const root = document.documentElement;
try {
const saved = localStorage.getItem('blog.theme.scheme');
const scheme = saved === 'dark' || saved === 'light'
? saved
: (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
root.setAttribute('data-scheme', scheme);
} catch (_) {}
})();
</script>

这段脚本包含两个细节:

  1. 枚举值校验:只接受 'dark' 或 'light' 两个合法值,若本地存储值异常则回退到系统级媒体查询。
  2. try / catch 容错:在禁用 Storage 的隐私模式或受限 WebView 中,访问 localStorage 可能抛出 SecurityError,包裹 try / catch 可避免阻塞后续脚本执行。

WCAG 2.1 对比度计算与无障碍媒体查询

调整配色变量时,可按 W3C WCAG 2.1 相对亮度公式核算文本对比度(Contrast Ratio):

$$CR = \frac{\max(L_{\text{fg}}, L_{\text{bg}}) + 0.05}{\min(L_{\text{fg}}, L_{\text{bg}}) + 0.05}$$

普通正文文本要求 $CR \ge 4.5:1$(AA 级),大号标题文本要求 $CR \ge 3.0:1$。同时,在样式表中适配操作系统的三类无障碍媒体查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* 1. 减弱动态效果偏好:关闭平滑滚动与过渡动画 */
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

/* 2. 高对比度偏好:加深卡片分割线与次要文本对比度 */
@media (prefers-contrast: more) {
:root {
--border-color: rgba(15, 23, 42, 0.28);
--text-muted: #334155;
}
html[data-scheme="dark"] {
--border-color: rgba(248, 250, 252, 0.32);
--text-muted: #cbd5e1;
}
}

/* 3. 键盘导航可见焦点环:仅在 Tab 键聚焦时显示轮廓 */
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: 4px;
}

配色与无障碍自检表

检查项 核对标准 验证方法
正文阅读对比度 浅色与深色下 --text-main 相对 --bg-card 对比度均 $\ge 14:1$ 使用浏览器 DevTools CSS Overview 或无障碍检查器验证
次要元信息对比度 日期、阅读时长等 --text-muted 对比度 $\ge 4.5:1$ 检查深浅模式下均无低于 4.5:1 的浅灰小字
首屏切换无白闪 切换至深色模式后连续按 F5 刷新或点击文章卡片跳转 首帧直接渲染深色底板 #0b0f17,无白屏闪动
键盘焦点可达性 在首页连续按 Tab 键遍历导航、搜索按钮与文章链接 每个交互元素均有清晰的 2px :focus-visible 焦点环