作者:冯伟尧 & 石嘉 & 刘大畅

切换暗色模式activity直接destroy_js

需求 & 现状

作为近两年的「新时尚」,暗色/深色模式已经几乎成为了各家系统、应用以及 Web 站点的标配功能。想要支持暗色模式,本质是判断在暗色模式下,更改生效的颜色值。在 Web 站点中,一种简单的做法是通过 CSS 变量[1] 来实现:

.button {
    background-color: var(--color-bg-0);
}
body {
    --color-bg-0: #fff;
}
body[theme-mode=dark] {
    --color-bg-0: #000;
}

CSS 变量就是一些自定义的属性,遵循正常的的样式覆盖原则。如上所示,button的背景颜色由 --color-bg-0 CSS 变量决定,当给 body 设置 <body theme-mode="dark">...</body> 时, --color-bg-0: #000 会覆盖 --color-bg-0: #fff,此时 button 背景色为黑色。

以上即是 Semi Design 实现暗色模式的大致原理。如果你想让自己的站点支持暗色模式,一个关键点是,CSS 样式中所有的颜色值必须使用 Semi Design 颜色变量(比如前面的--color-bg-0)。

现在的问题是,对于已有项目,为了支持暗色模式,必须将项目中所有颜色值手动替换为 Semi 颜色变量,这个过程非常繁琐且工作量巨大。因此本文将介绍 Semi 团队在内部项目中如何通过自动化的方式来解决这个问题。

为了避免理解歧义,这里统一名词概念:

  • 颜色字面量:指样式文件中确定的颜色值,如 white, #FFF, rgb(23,45,0) 等;
  • 颜色变量:指与颜色相关的 CSS 变量,这里特指 Semi 颜色变量,如 --color-primary

解决思路

主要关注两点:

关注点一:如何解析识别 css/scss/stylus 等样式文件中的【颜色字面量】,如:background: white 中的 white

关注点二:计算识别的【颜色字面量】是否可以替换为 Semi 主题包中提供的【颜色变量】如:var(--color-bg-0)。如果【颜色字面量】与【颜色变量】对应的颜色值相同或相近则认为可以替换。

对于第二点,计算是否有对应的颜色变量,可以使用 chorma-js[2]。chorma-js 可以实现任意颜色格式之间的转换以及计算颜色之间的相似度,比如:chroma.distance(color1, color2)

更重要的是第一点,如何解析识别【颜色字面量】,此时首先想到的是 PostCSS。

PostCSS[3]

PostCSS is a tool for transforming styles with JS plugins. These plugins can lint your CSS, support variables and mixins, transpile future CSS syntax, inline images, and more.

PostCSS 与 CSS 的关系,就相当于 Babel 与 JavaScript。PostCSS 可以将 CSS 解析为抽象语法树 (AST),并提供了 API 允许分析和转换 CSS 文件的内容。

如下图所示,左边为一段 CSS 代码,右边为使用 PostCSS 解析后的结果(可访问 AST explorer 站点[4] 体验):



切换暗色模式activity直接destroy_js_02

不仅仅是 CSS,通过引入插件,PostCSS 可以解析任何形式的样式代码,比如 Sass, Less, Stylus以及 JSX 中的样式代码。

如何识别颜色值

CSS 中指定颜色值的方式有三类,分别为:

  • 颜色关键字,如:white, blue 等等;
  • RGB 16 进制形式,如:#FFF;
  • 函数形式,如:rgb(255, 255, 0) 或者 rgba(255, 0, 255, 0.5)hsl(0deg, 0%, 13%) 或者 hsla(0deg, 0%, 13%, 1) 或者 hwb(...) gray(...);

因此借助 PostCSS 解析后的结果,针对以上三种形式的颜色值,识别算法为:

  • CSS 属性值中是否包含穷举的 颜色关键字[5]
  • CSS 属性值以 '#' 开头则认为是 RGB 16 进制形式
  • CSS 属性值为函数形式且名称为 rgb, rgba, hsl, hsla 等则认为是函数形式

由此,基于 PostCSS 我们可以完成样式的解析以及颜色的识别。如果要达到自动完成项目中样式文件的颜色值替换工作,只需要添加收集项目中的样式文件的逻辑,并封装为 CLI 工具方便使用即可。

PostCSS 已经能够满足我们的需求,然而,还有另一种实现方式,Stylelint。

Stylelint[6]

A mighty, modern linter that helps you avoid errors and enforce conventions in your styles.

Stylelint 与 CSS 的关系,就相当于 ESLint 与 JavaScript 的关系。Stylelint 是针对样式文件的代码审查工具。Stylelint 底层基于 PostCSS,对样式文件进行解析分析,可以对 CSS/Sass/Stylus/Less 等样式文件进行审查。

比如 Stylelint 其中一个规则,color-named,不允许使用颜色关键字,效果如下:



切换暗色模式activity直接destroy_java_03

当我们在代码中使用了类似 white 颜色关键字,Stylelint 会给出提示,借助编辑器插件,我们能够在编码过程中获得实时提示,非常方便。

每个规则在 Stylelint 中都是一个插件,插件的输入是样式文件通过 PostCSS 解析后的抽象语法树,插件可以基于 PostCSS API[7] 遍历语法树,分析/修改样式代码。因此类似规则 color-named,我们也可以实现一个 Stylelint 插件,分析并识别样式文件中的【颜色字面量】,并给出提示,针对有对应的 Semi 颜色变量的,支持自动替换(autofix)。具体可以查看官方的插件开发文档[8]

相比 PostCSS,使用 Stylelint 的好处是:

  1. 不需要关心样式文件的解析,只需要关注拿到解析结果后如何分析
  2. 不需要关心根据不同的样式语言,引入不同的 PostCSS 解析插件
  3. StyleLint 有完善的 CLI 工具以及 VS Code 插件,可以直接复用

综上我们最终基于 Stylelint 来实现需求。

最终的能力 & 使用方式

配合 Stylelint 可以达到如下效果。

  • 通过 StyleLint CLI 命令行工具一键【查找 & 替换】所有颜色变量;
  • 通过 Stylelint 编辑器插件,写码过程中提示哪些【颜色字面量】没有使用 Semi 【颜色变量】,可设置保存时自动替换;

Step 1: 安装 npm 包

$ npm i -D stylelint @ies/stylelint-semi

Step 2: 添加/更改配置文件

根目录下添加 .stylelintrc.json 文件,配置 @ies/stylelint-semi 插件与 semi/color-no-literal 规则:

{
  "plugins": ["@ies/stylelint-semi"],
  "rules": {
    "semi/color-no-literal": [true, { "severity": "warning", "fixExact": false }]
  }
}

Step 3: 执行 CLI 命令

$ npx stylelint "**/*.scss"

执行后,会检测项目下所有 .scss 文件,以下写法将会得到提示:

.banner {
    color: white;
    background: #eee;
    border: 1px solid rgb(0, 0, 0);
}



切换暗色模式activity直接destroy_css_04

如果要替换为 Semi 颜色变量,可以使用:

$ npx stylelint "**/*.scss" --fix

替换后的结果为:

.banner {
    color: var(--color-white);
    background: var(--color-tertiary-light-hover);
    border: 1px solid var(--color-black);
}

Step 4: 编辑器提示

VS Code 中安装 Stylelint 插件[9]。此时鼠标 hover 到颜色值会有以下提示:



切换暗色模式activity直接destroy_html_05

实际效果展示

支持暗色模式前后的效果:

浅色版



切换暗色模式activity直接destroy_编程语言_06

深色版



切换暗色模式activity直接destroy_js_07

欢迎反馈



切换暗色模式activity直接destroy_css_08

这里是 Semi Design 团队,Semi 是由互娱社区前端团队与 UED 团队共同设计开发并维护的设计系统。包括设计语言、React 组件库、物料市场、Semi DV、主题商店等一整套全面、易用、优质的中后台解决方案。帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

参考资料

[1]

CSS 变量: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties


[2]

chorma-js: https://github.com/gka/chroma.js/


[3]

PostCSS: https://github.com/postcss/postcss


[4]

AST explorer 站点: https://astexplorer.net/#/2uBU1BLuJ1


[5]

颜色关键字: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#color_keywords


[6]

Stylelint: https://github.com/stylelint/stylelint


[7]

PostCSS API: https://api.postcss.org/


[8]

官方的插件开发文档: https://stylelint.io/developer-guide/rules#add-autofix


[9]

Stylelint 插件: https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint


[10]

Postcss plugin 开发文档: https://github.com/postcss/postcss/blob/main/docs/writing-a-plugin.md


[11]

Stylelint rule 开发: https://stylelint.io/developer-guide/rules


[12]

AST 调试工具: https://astexplorer.net/#/2uBU1BLuJ1