主题
主题系统
本文引用的源码文件
本文引用的文件
目录
- 引言
- 项目结构与主题入口
- Token 体系与语义化命名
- 亮暗主题切换机制
- WCAG AA 对比度合规与审计
- 主题迁移工具与映射表
- 浏览器原生 color-scheme 与 UA 控件适配
- 性能优化策略
- 扩展指南:新增变体、自定义 Token 与主题 API
- 依赖关系分析
- 故障排查
- 结论
引言
本主题系统围绕 CSS 变量 Token 体系构建,统一承载表面、文本、品牌、状态、边框等语义色,并通过 data-theme 属性在亮色、暗色之间切换。系统同时提供静态门禁(映射检查、对比度校验)和动态审计(Playwright 真实渲染扫描),确保暗色模式下的可访问性达到 WCAG AA 要求。迁移脚本支持将旧版硬编码色值批量替换为 Token,并保留受控白名单以兼容历史遗留场景。
项目结构与主题入口
主题相关代码集中在共享层与脚本层:
- 样式 Token 定义位于共享样式文件,按亮/暗两块声明。
- 主题运行时逻辑负责读取偏好、解析生效主题、写入 DOM 属性并订阅系统主题变化。
- 脚本层提供暗色审计、对比度门禁和旧样式迁移能力。
graph TB ThemeCSS["theme.css<br/>Token 定义"] ThemeJS["theme.js<br/>主题运行时"] Audit["theme-dark-audit.mjs<br/>动态审计"] Migrate["theme-migrate.py<br/>迁移脚本"] Map["theme-map.json<br/>映射表"] Contrast["theme-contrast-check.py<br/>对比度门禁"] ThemeJS --> ThemeCSS Audit --> ThemeJS Audit --> ThemeCSS Migrate --> Map Contrast --> ThemeCSS
图表来源
- src/shared/theme.css#L1-L289
- src/shared/theme.js#L1-L90
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-migrate.py#L1-L327
- scripts/theme-map.json#L1-L424
- scripts/theme-contrast-check.py#L1-L114
章节来源
Token 体系与语义化命名
Token 体系按语义分类组织,避免直接写死颜色值:
- 表面类:页面背景、卡片背景、悬浮层背景、凹陷背景、禁用背景、悬停/激活态背景。
- 文本类:主文本、次文本、三级文本、禁用文本、反色文本、链接文本、品牌上文本。
- 品牌类:主品牌色、强调色、紫色系品牌、品牌柔光背景与边框、品牌渐变。
- 状态类:危险、成功、警告、信息,分别包含主色、强色、柔光文本、柔光背景、柔光边框及 RGB 辅助变量。
- 边框与分隔:弱边框、默认边框、强边框、分割线。
- 浮层与阴影:遮罩、叠加 RGB、多级阴影、浮层 elevation、提示框背景、滚动条滑块、选区背景。
- 固定不翻转:纸面语义、终端代码块前景/背景在两主题下保持固定。
- Toast 与实心填充:语义色作为浮层背景或按钮填充,暗色模式下采用深饱和档保证白字可读。
命名规范要点:
- 使用语义前缀如
--text-*、--brand-*、--danger-*、--success-*、--warning-*、--info-*、--bg-*、--border-*。 - 对需要透明度的颜色提供对应 RGB 辅助变量,例如
--brand-rgb、--overlay-rgb、--surface-rgb,便于在rgba()中复用。 - 对渐变与阴影等复合值,优先引用已有 Token,避免引入新的裸色。
classDiagram
class Tokens {
+表面
+文本
+品牌
+状态_危险
+状态_成功
+状态_警告
+状态_信息
+边框与分隔
+浮层与阴影
+Toast与填充
+RGB辅助变量
}
Tokens : "语义化命名"
Tokens : "亮/暗双块覆盖"
图表来源
章节来源
亮暗主题切换机制
主题切换由运行时模块管理,核心流程如下:
- 偏好值
preference可为light、dark、system,持久化于存储键appTheme。 - 生效值
resolved为light或dark,写入<html data-theme>,供 CSS 选择器覆盖。 - 防闪烁:模块顶层同步读取本地缓存
appThemeResolved,立即设置一次data-theme,再异步加载正式偏好。 - 系统主题监听:当偏好为
system时,监听prefers-color-scheme: dark媒体查询变化,自动更新生效主题。 - 跨环境同步:监听扩展存储变更事件,当其他标签页修改
appTheme时,当前页同步应用新主题。 - 订阅机制:主题生效值变化时通知所有订阅者,用于 UI 刷新或联动逻辑。
sequenceDiagram
participant User as "用户操作"
participant ThemeJS as "theme.js"
participant Storage as "chrome.storage.local"
participant DOM as "document.documentElement"
participant MQ as "matchMedia"
User->>ThemeJS : setTheme('dark' | 'light' | 'system')
ThemeJS->>Storage : 写入 appTheme
ThemeJS->>ThemeJS : resolve(preference)
ThemeJS->>DOM : 设置 data-theme=resolved
ThemeJS->>ThemeJS : 触发订阅者回调
Note over ThemeJS,MQ : 若 preference='system',监听系统主题变化
MQ-->>ThemeJS : prefers-color-scheme 变化
ThemeJS->>ThemeJS : apply(resolve('system'))
图表来源
章节来源
WCAG AA 对比度合规与审计
系统通过三层保障暗色模式的可访问性:
- 静态对比度门禁:解析 theme.css 的暗色块,计算文本类与图形类 Token 相对于暗色背景的对比度,低于阈值即失败。
- 动态审计探针:使用 Playwright 加载真实扩展产物,切换到暗色后遍历已渲染元素,实测前景/背景合成后的对比度,并检测“白斑”(暗色下不该出现的近纯白大块背景)。
- 内容态补充:对需交互才出现的内容态元素(如工具调用状态、搜索高亮),由静态门禁与迁移脚本配合保证,不在动态审计范围内。
对比度口径:
- 正文/状态文本类阈值 4.5:1。
- 图形/强调类阈值 3:1。
- 装饰性分隔(边框、分割线、滚动条滑块)刻意低调,仅记录 INFO 不计入 AA 失败。
flowchart TD
Start(["启动审计"]) --> BuildCheck["检查 dist 是否构建"]
BuildCheck --> |否| ExitFail["退出并报错"]
BuildCheck --> |是| LaunchBrowser["启动 Chromium 上下文"]
LaunchBrowser --> LoadExtension["加载扩展产物"]
LoadExtension --> SetDark["设置 appTheme=dark 并等待 data-theme=dark"]
SetDark --> ScanSidePanel["侧边栏巡检"]
ScanSidePanel --> InjectSynthetic["注入 Toast/弹窗控件进行回归防护"]
InjectSynthetic --> ScanOptions["配置页 Tab 巡检"]
ScanOptions --> EvaluateAudit["注入审计函数计算对比度与白斑"]
EvaluateAudit --> Summarize["汇总违规与白斑"]
Summarize --> Pass{"是否有违规或白斑?"}
Pass --> |是| ExitFail
Pass --> |否| ExitPass["审计通过"]
图表来源
章节来源
主题迁移工具与映射表
迁移工具基于映射表将旧版硬编码色值替换为 Token,并提供多种工作模式:
- 直接映射:十六进制色值到 Token 名称的直接映射。
- 上下文分发:同一色值在不同属性上下文中映射到不同 Token(例如白色在
color与background下不同处理)。 - RGBA 家族:将常见三元组归一化为 RGB 辅助变量,减少重复透明度值。
- 白名单:全局豁免与文件作用域豁免,避免误改导出模板、内联 SVG 或历史遗留字面值。
- 模式选项:
--check:仅报告未覆盖的裸色,命中即非零退出。--missing:输出 styles.css 中未覆盖的十六进制色值。--missing-rgba:输出未归属族的 rgba 三元组。--ranges:限定行段范围。--mode js:处理 JS 中的样式字符串与属性赋值。--css-rules:对 CSS 规则体逐声明处理。
flowchart TD
Start(["运行迁移脚本"]) --> ParseArgs["解析参数与目标文件"]
ParseArgs --> Mode{"工作模式?"}
Mode --> |check| CheckMode["扫描裸色与 rgba 三元组"]
Mode --> |missing| MissingHex["输出未覆盖 hex"]
Mode --> |missing-rgba| MissingRGBA["输出未归属族 rgba"]
Mode --> |apply| ApplyMode["逐行映射替换"]
CheckMode --> ReportCheck["输出未覆盖项并返回码"]
MissingHex --> End
MissingRGBA --> End
ApplyMode --> Review["收集需人工复核项"]
Review --> WriteBack["写回文件"]
WriteBack --> End(["结束"])
图表来源
章节来源
浏览器原生 color-scheme 与 UA 控件适配
主题系统使用浏览器原生 color-scheme 属性提升 UA 控件的主题体验:
- 亮色根节点设置
color-scheme: light,暗色根节点设置color-scheme: dark。 - 原生控件(复选框、单选框、滑块、颜色选择器、进度条)通过
accent-color统一为主题品牌强调色。 - Markdown 库在暗色下写死亮色,主题通过选择器覆盖段落、标题、链接、代码、表格、引用、删除线等,使其跟随 Token。
graph TB RootLight[":root<br/>color-scheme: light"] RootDark["html[data-theme='dark']<br/>color-scheme: dark"] UAControls["input[type='checkbox'|radio|range|color], progress<br/>accent-color: var(--brand-500)"] MarkdownDark["html[data-theme='dark'] .markdown-body<br/>覆盖标题/链接/代码/表格等"] RootLight --> UAControls RootDark --> UAControls RootDark --> MarkdownDark
图表来源
- src/shared/theme.css#L8-L10
- src/shared/theme.css#L125-L127
- src/shared/theme.css#L235-L242
- src/shared/theme.css#L244-L289
章节来源
性能优化策略
主题系统的性能优化主要体现在以下方面:
- CSS 变量惰性解析:浮层 elevation 等复合值通过
var()引用边框与叠加 RGB,只需在根节点定义一次,随主题自适应,避免重复计算。 - 减少重绘重排:主题切换仅修改
data-theme属性,CSS 选择器覆盖整块 Token,避免频繁操作 DOM 样式。 - 防闪烁:同步读取本地缓存提前设置
data-theme,降低首次渲染时的主题抖动。 - 内存管理:主题订阅者数组仅在变化时触发回调,避免无意义刷新;媒体查询监听与存储监听绑定一次,防止重复注册。
- 迁移脚本增量处理:按行逐声明处理,避免整文件重写带来的额外开销,并在 JS 模式下区分 CSS 规则体与单值赋值,减少误判。
章节来源
扩展指南:新增变体、自定义 Token 与主题 API
新增主题变体
当前主题为亮/暗双态,如需新增变体(例如“高对比度”或“护眼模式”),建议:
- 在 Token 文件中新增对应选择器块(例如
html[data-theme="high-contrast"]),覆盖关键 Token。 - 在运行时模块中扩展有效值集合与解析逻辑,允许新变体作为偏好值。
- 在 UI 中暴露切换入口,并同步更新
appTheme存储。 - 更新对比度门禁与动态审计,确保新变体符合 WCAG AA。
注意:当前实现以 light、dark、system 为主,新增变体需同步修改运行时校验与存储键行为。
自定义 Token
- 在 Token 文件中新增语义化变量名,遵循现有命名规范。
- 在亮/暗两块分别定义具体色值,确保暗色下对比度达标。
- 如需透明度,新增对应 RGB 辅助变量,并在迁移映射表中登记常见三元组。
- 在迁移脚本映射表中添加旧色值到新 Token 的映射,执行
--check验证无遗漏。
使用主题 API
- 获取当前偏好与生效主题:调用
getTheme()与getResolvedTheme()。 - 订阅主题变化:调用
subscribeTheme(callback),在回调中处理 UI 刷新。 - 设置主题:调用
setTheme(pref),传入light、dark或system。 - 初始化主题:调用
initTheme(),完成媒体查询监听与存储监听注册。
示例路径参考:
- 主题 API 定义位置:src/shared/theme.js#L50-L89
- 主题 Token 定义位置:src/shared/theme.css#L8-L228
章节来源
依赖关系分析
主题系统内部依赖清晰:
- 运行时模块依赖 DOM 与浏览器存储 API,负责主题状态管理与事件监听。
- 样式文件依赖 Token 变量,被运行时通过
data-theme选择器覆盖。 - 审计脚本依赖 Playwright 与 Node 文件系统,驱动真实扩展加载与元素扫描。
- 迁移脚本依赖映射表 JSON,提供静态分析与批量替换能力。
- 对比度门禁脚本依赖正则解析与亮度计算,确保 Token 质量。
graph LR ThemeJS["theme.js"] --> DOM["DOM API"] ThemeJS --> Storage["chrome.storage.local"] ThemeCSS["theme.css"] --> Selectors["data-theme 选择器"] Audit["theme-dark-audit.mjs"] --> Playwright["Playwright"] Audit --> FS["Node fs/path"] Migrate["theme-migrate.py"] --> MapJSON["theme-map.json"] Contrast["theme-contrast-check.py"] --> Regex["正则解析"]
图表来源
- src/shared/theme.js#L1-L90
- src/shared/theme.css#L1-L289
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-migrate.py#L1-L327
- scripts/theme-map.json#L1-L424
- scripts/theme-contrast-check.py#L1-L114
章节来源
- src/shared/theme.js#L1-L90
- src/shared/theme.css#L1-L289
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-migrate.py#L1-L327
- scripts/theme-map.json#L1-L424
- scripts/theme-contrast-check.py#L1-L114
故障排查
常见问题与定位方法:
- 主题未切换:检查
data-theme属性是否正确设置,确认initTheme()已调用且存储读取未失败。 - 暗色下文字不可读:运行对比度门禁与动态审计,定位具体 Token 或元素,调整色值或背景。
- 迁移脚本误报:检查白名单与作用域豁免,确认色值是否属于历史遗留或特殊用途。
- UA 控件未适配:确认
color-scheme与accent-color是否生效,检查 Markdown 覆盖规则。 - 性能问题:避免频繁修改
data-theme,尽量批量更新 Token,减少 DOM 操作。
章节来源
- src/shared/theme.js#L54-L82
- scripts/theme-dark-audit.mjs#L141-L257
- scripts/theme-migrate.py#L249-L323
- src/shared/theme.css#L235-L289
结论
该主题系统以语义化 Token 为核心,结合运行时切换、静态门禁与动态审计,形成完整的亮暗主题解决方案。通过 data-theme 与 color-scheme 的配合,既保证了界面一致性,又提升了 UA 控件的可访问性。迁移工具与映射表为历史样式提供了平滑升级路径,而对比度门禁与审计脚本则确保了暗色模式下的 WCAG AA 合规。未来扩展可在保持现有架构的前提下,按需增加变体与 Token,同时持续完善自动化审计流程。