Skip to content

主题系统 ​

本文引用的源码文件

本文引用的文件

目录 ​

  1. 引言
  2. 项目结构与主题入口
  3. Token 体系与语义化命名
  4. 亮暗主题切换机制
  5. WCAG AA 对比度合规与审计
  6. 主题迁移工具与映射表
  7. 浏览器原生 color-scheme 与 UA 控件适配
  8. 性能优化策略
  9. 扩展指南:新增变体、自定义 Token 与主题 API
  10. 依赖关系分析
  11. 故障排查
  12. 结论

引言 ​

本主题系统围绕 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

图表来源

章节来源

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

图表来源

章节来源

性能优化策略 ​

主题系统的性能优化主要体现在以下方面:

  • 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(),完成媒体查询监听与存储监听注册。

示例路径参考:

章节来源

依赖关系分析 ​

主题系统内部依赖清晰:

  • 运行时模块依赖 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["正则解析"]

图表来源

章节来源

故障排查 ​

常见问题与定位方法:

  • 主题未切换:检查 data-theme 属性是否正确设置,确认 initTheme() 已调用且存储读取未失败。
  • 暗色下文字不可读:运行对比度门禁与动态审计,定位具体 Token 或元素,调整色值或背景。
  • 迁移脚本误报:检查白名单与作用域豁免,确认色值是否属于历史遗留或特殊用途。
  • UA 控件未适配:确认 color-scheme 与 accent-color 是否生效,检查 Markdown 覆盖规则。
  • 性能问题:避免频繁修改 data-theme,尽量批量更新 Token,减少 DOM 操作。

章节来源

结论 ​

该主题系统以语义化 Token 为核心,结合运行时切换、静态门禁与动态审计,形成完整的亮暗主题解决方案。通过 data-theme 与 color-scheme 的配合,既保证了界面一致性,又提升了 UA 控件的可访问性。迁移工具与映射表为历史样式提供了平滑升级路径,而对比度门禁与审计脚本则确保了暗色模式下的 WCAG AA 合规。未来扩展可在保持现有架构的前提下,按需增加变体与 Token,同时持续完善自动化审计流程。