几年前,如果有人提到用 JavaScript 编写 HTML 作为构建大型网站的一种方式,很多开发者会当这作不可理喻的想法,但是现在,使用 React、Vue 和 Angular 框架为组件开发的应用正在慢慢替代传统的 Web 开发。
现在 CSS-in-JS 确实也有点像当年的味道,虽然并不是唯一的解决方案,却提供了一个很大胆的想法和尝试。
对现代化的 Web 开发项目说,CSS 也是如此,CSS 做为 Web 的样式表来呈现丰富多彩的 Web 应用已经不再是唯一的选择了,我们或许应该多考虑其他的扩展性和移植性尝试未来的 CSS-in-JS。
一 CSS 的介绍
CSS(层叠样式表)是一种用来为结构化文档添加样式的计算机语言,由 W3C 定义和维护。目前最新版本是 CSS2.1,为 W3C 的推荐标准。CSS3 现在已被大部分现代浏览器支持,而下一版的 CSS4 仍在开发中。
1 模块和标准化进程
CSS Level 2 经历了 9 年的时间(从 2002 年 8 月到 2011 年 6 月)才达到 Recommendation(推荐) 状态,主要原因是被一些次要特性拖了后腿。为了加快那些已经确认没有问题的特性的标准化速度,W3C 的 CSS Working Group(CSS 工作组) 作出了一项被称为 Beijing doctrine 的决定,将 CSS 划分为许多小组件,称之为_模块_。这些模块彼此独立,按照各自的进度来进行标准化。其中一些已经是 W3C Recommendation 状态,也有一些仍是 early Working Drafts(早期工作草案)。当新的需求被肯定后, 新的模块也会同样地添加进来。
第一个 CSS 于1996年推出,下面是 CSS 版本的时间表:
2 CSS 模块状态
从形式上来说,CSS3 标准自身已经不存在了。每个模块都被独立的标准化,现在标准 CSS 包括了修订后的 CSS2.1 以及完整模块对它的扩充,模块的 level(级别)数并不一致。可以在每个时间点上为 CSS 标准定义一个 snapshots(快照),列出 CSS 2.1 和成熟的模块。
W3C 会定期的发布这些 snapshots,如 2007, 2010, 2015 或 2017。
目前为止,还没有 level 超过 3 的模块被标准化,未来应该会有所改变。不过有些模块,比如 Selectors(选择器)4 或 CSS Borders and Backgrounds(边框和背景)Level 4 早已拥有了 Editor's Draft(编辑草案),即使它们还没达到 First Published Working Draft(初次发布工作草案)状态。
3 五种 CSS 设计模式
现代化的前端开发在历史上发展了许多的 CSS 设计模式,主要发展出以下几种:
- OOCSS(Object Oriented CSS)
- SMACSS(Scalable and Modular Architecture for CSS)
- BEM(Block - Element - Modifier)
- ITCSS(Inverted Triangle Cascading Style Sheets)
- Atomic CSS
其设计的原因基本是基于这几个问题来做优化的:
- 减少选择器命名和样式的冲突
- 清晰的 CSS 整体结构
- 去除冗余代码,减少样式的体积
- 可重复利用,组件化的 CSS
- 提高 CSS 代码的可读性
4 Atomic CSS 的历史
- 2013/06/10:Brad Frost 发布了 Atomic Design 文章,在社区上有一些文章开始讨论 Atomic CSS
- 2015/01/08:《atomic design: the book》 一书发布
- 2014/10/02:atomizer 项目创建
- 2017/10/06:tailwindcss 项目创建
Tailwind CSS 和其他预编译器相比还是比较的冷门,如下图:
在 React 和 Vue 日益吞噬的 Web 开发界中,组件化的思想和工程化日渐成熟,Atomic CSS 也算是比较早推出的一个设计思想,笔者觉得 Atomic CSS 能做的事情,在 CSS-in-JS 反而能做的更好,因为 JS 框架和工具的盛行和丰富,Atomic(原子化)也是未来 CSS-in-JS 一个可以涉足的区域。
5 CSS 数学表达式
根据 CSSWG 的 draft,CSS 目前支持计算的数学表达式主要包含五大类:
- 基本算数:calc()
- 比较函数:min(), max(), clamp()
- 步进函数:round(), mod(), rem()
- 三角函数:sin(), cos(), tan(), asin(), acos(), atan(), atan2()
- 指数函数:pow(), sqrt(), hypot(), log(), exp()
日常使用中 calc() 算是最常用的,一般用来计算长宽、响应式布局等等,而比较函数在一些场景也可能会用的上,剩下的其他函数很大部分都没有机会在项目中使用的上。
6 CSS Houdini
Houdini是一组底层API,它们公开了CSS引擎的各个部分,从而使开发人员能够通过加入浏览器渲染引擎的样式和布局过程来扩展CSS。Houdini是一组API,它们使开发人员可以直接访问CSS 对象模型 (CSSOM),使开发人员可以编写浏览器可以解析为CSS的代码,从而创建新的CSS功能,而无需等待它们在浏览器中本地实现。
—— 《MDN / CSS Houdini》
如果说 CSS-in-JS 是用现有的标准用 JS 去控制、扩展和实时联动 CSS 的一套方案,那么 CSS Houdini 就相当于进阶版本的 CSS-in-JS,通过公开 CSS 引擎的各个功能,是开发人员能更好的扩展 CSS,笔者认为是不是也可以理解为 CSS Houdini 的出现也代表了现在的纯 CSS 已经很难满足现在日益丰富的 Web 应用。
CSS Houdini
CSS Parser API
这是直接地暴露出 CSS 解析器的 API接口,能够把任意 CSS 类语言解析成为一种中间类型,定义新的结构。
CSS Properties and Values API
- 定义一个用来注册新的 CSS 属性的 API。通过该 API 注册的属性必须用一种特定的解析语法书写,以定义其类型、继承行为以及初始值。
- CSS Properties and Values API reference
- CSS Properties and Values API guide
CSS Typed OM
- 可以把 CSS Typed OM 视为 CSSOM 2.0,它的目的在于解决目前模型的一些问题,并实现 CSS Parsing API 和 CSS 属性与值 API 相关的特性。
- CSS Typed OM reference
- CSS Typed OM guide
CSS Layout API
被设计来提升 CSS 扩展性的 API,该 API 能够让开发者去书写他们自己的布局算法,比如 masonry 或者 line snapping。
CSS Painting API
- 被设计来提升 CSS 扩展性的 API,该 API 允许开发者通过 paint() 方法来写 JavaScript 函数,以控制绘制页面元素的样式或内容区域。
- CSS Painting API reference
- CSS Painting API guide
Worklets
- 该 API 允许脚本独立于 JavaScript 执行环境,运行在渲染流程的各个阶段。
- Worklets 在很接近于 JS 的 Web Workers ,由渲染引擎扩展并调用。
- Worklets reference
7 CSS 预处理器 (CSS Preprocessor)
CSS 预处理器是一个能让你通过预处理器自己独有的语法来生成 CSS 的程序。市面上有很多 CSS 预处理器可供选择,且绝大多数 CSS 预处理器会增加一些原生 CSS 不具备的特性,例如代码混合,嵌套选择器,继承选择器等。这些特性让 CSS 的结构更加具有可读性且易于维护。
—— 《MDN / CSS 预处理器》
一些最流行的 CSS 预处理器:
- PostCSS:2013/11/04
- Less:2009
- SASS:2006/11/28
- Stylus:2010/12/29
图中看到 PostCSS 的下载量一直遥遥领先其他 CSS 预处理器,PostCSS 比较大的优势在于社区有很多插件可以使用,相当于 CSS 届的 Babel,常见 PostCSS 插件如下:
- Autopre?xer:自动补全浏览器私有前缀
- precss:CSS 预处理(整合 Sass、LESS 或 Stylus 功能,语法基本和 Sass 的相同)
- postcss-import:通过 @import,整合多个 CSS 文件
- css-mqpacker:将相同的 CSS 媒体查询规则合并为一个
- cssnano:压缩 CSS 文件
- postcss-color-rgba-fallback:给 rgba 颜色创建降级方案(添加备用颜色)
- postcss-opacity:给 opacity 提供降级方案(给 IE 浏览器添加滤镜属性)
- node-pixrem:让 IE8 ?持 rem 单位
- postcss-pseudoelements:将伪元素的 :: 转换为 : ( IE8 不不?支持 ::)
如果一定需要使用 CSS 预处理器,可能 PostCSS 是最好的选择之一,当然,也是需要看实际你项目的整体方案来选择。
8 CSS-in-JS VS CSS Preprocessor
在 Google Trends 中我们可以看到 2014 年后 CSS-in-JS 的趋势就逐渐超越了 CSS 预处理器,这在一方面也说明了开发人员在 CSS-in-JS 上有着很大兴趣。
二 CSS-in-JS 的介绍
CSS-in-JS是一种样式化技术,其中 JavaScript 用于样式化组件。解析此 JavaScript 时,将生成 CSS(通常作为<style>元素)并将其附加到 DOM 中。它允许使用JavaScript以声明性和可维护的方式描述样式,从而将 CSS 抽象到组件级别本身。
1 CSS-in-JS 起源历史
- 2000年11月13日:W3C 草案中 Document Object Model (DOM) Level 2 Specification 提出了 CSS Object Model (CSSOM),允许 CSS 通过 JavaScript 操纵的。它非常类似于 DOM,但是用于 CSS 而不是 HTML。它允许用户动态读取和修改 CSS 样式。
- 2014年11月15日:CSS-in-JS 由 Facebook 的员工 Vjeux 在 NationJS 会议上提出:可以借用 JS 解决许多 CSS 本身的一些“缺陷”,比如全局作用域、死代码移除、生效顺序依赖于样式加载顺序、常量共享等等问题。
- 2014 ~ 现在:大量的 CSS-in-JS 的解决方案的提出,在领域上不断除旧推新,在工程化和框架的解决方案中不断探索实现。
CSS-in-JS 的一大特点是它的方案众多,这种看似混乱的状态很符合前端社区喜欢重复造轮子的特征。发展初期,社区在各个方向上探索着用 JS 开发和维护 CSS 的可能性。每隔一段时间,都会有新的语法方案或实现,尝试补充、增强或是修复已有实现。
2 没有 CSS 的那些平台和框架
- QT:QStyle Class & Draw Method
- Flutter:Style Object
- ReactNative:ReactNative.StyleSheet
- Unreal Engine:Style Object
- Canvas:Draw Method
- Skia:Draw Method
都是基于各自的设计 imperative & declarative(命令式和声明式)的样式编写,能与程序设计中的各个状态绑定,并不局限于样式表修改这一概念。
3 区别是什么
如果说纯 CSS 框架工具和 CSS-in-JS 的区别是什么,笔者觉得最大的区别就是编译运行的不同时机,我们可以理解成:CSS 框架工具只等于 AOT(Ahead-of-time),CSS-in-JS 则拥有 JIT(Just-in-time) 的能力,例如上面提到的 CSS Houdini API 本质其实也是相当于扩展 CSS 框架的实时运行的能力,而 JIT 的框架和工具本质上也可以使用 AOT 的工具来优化,例如 Babel 和 Webpack。
4 使用 CSS-in-JS 的优点
- 组件化思考模式,不再需要维护一堆样式表。CSS-in-JS 将 CSS 模型抽象到组件级别,而不是文档级别(模块化)。
- CSS-in-JS 利用 JavaScript 环境的全部功能来增强CSS。
- 真正的选择器隔离。范围选择器是不够的。CSS具有从父元素自动继承的属性(如果未明确定义)。
- CSS 要避免选择器冲突,例如 BEM 之类的命名约定可能在一个项目中有所帮助,但在集成第三方代码时则会存在很多问题。当 JSS 将 JSON 表示形式编译为 CSS 时,默认情况下会生成唯一的类名。
- 动态浏览器私有化前缀,使用 CSS-in-JS 可以避免臃肿的 CSS 代码。
- 代码共享,轻松在 JS 和 CSS 之间共享常量和函数。
- CSS-in-JS 的单元化测试。
- TypeScript 的支持。
- 减少项目编译的依赖,纯 JS 或 TS 项目。
- 动态变化的主题和变量。
5 使用 CSS-in-JS 的缺点
- 学习曲线,需要学习使用
- 新的依赖
6 那些流行的 CSS-in-JS 库
Run-Time(JIT)
运行时动态修改样式的库:
- emotion
- jss
- styled-components
- aphrodite
- radium
- glamor
如下图统计,emotion、jss 和 styled-components 都有不错的开发者 NPM 下载使用量,保持长期的增长趋势,这对开发者来说是比较不错的,意味着这些库也有稳定的发展和维护。
逐年递增的下载数量反映了开发社区和使用范围的扩大,也表明了开发者在 CSS-in-JS 上的积极贡献和参与。
这些库大部分的动态修改样式主要使用这几种方式:
1)CSS 样式表
- Scoped CSS:通过每个组件添加 CSS 样式表,但是添加了 scoped 的作用域
- Global CSS:在 HTML 全局添加修改样式表的 Content 来修改样式
2)CSSOM 修改
- 通过修改全局的 CSSOM 的 CSSRule 来达到修改样式的目的
这几种方式,笔者比较推荐 CSSOM 修改的方式,页面的 HTML 结构和内容不会变化,也不会有过多的单组件 CSS,而且在修改样式方式上也有很多可以优化和扩展的余地,期望后续的开发者能有优秀的实践可推广。
Build-Time(AOT)
提前编译成 CSS 样式表的库:
- Linaria
提前编译的优势在于一些小程序和其他框架需要 CSS 样式表时是唯一的选择,在用户低端手机和性能上比动态修改样式要更有优势。
7 都有谁在使用?
UI 库
material-ui 是笔者很早关注的一个 material design 的一个开源 UI 组件库,用过 ReactJS 的开发同学可能有了解过,记得一开始官方采用的是内联样式,后续研发了自己的一套 CSS-in-JS 的实现方案,单独发布了 Material-UI 组件中使用的样式方案 —— @material-ui/styles。
公司
数千家公司正在使用 CSS-in-JS 进行开发 Web 应用。
- Patreon
- Target
- Atlassian
- Vogue
- GitHub
- Coinbase
8 Chrome Devtools 对 CSS-in-JS 的支持
在 What's New In DevTools (Chrome 85) 中 Google 更新了 CSS-in-JS 框架的样式编辑的支持。
现在,“Styles”窗格对编辑使用 CSS 对象模型(CSSOM)API 创建的样式提供了更好的支持。许多 CSS-in-JS 框架和库都在底层使用 CSSOM API 来构造样式。
现在也可以使用 “Constructable Stylesheets” 编辑在 JavaScript 中动态添加的样式。
可构造样式表是使用 Shadow DOM 时创建和修改样式的一种新的方法。
例如,(CSSOM API)h1添加的样式 CSSStyleSheet以前不可编辑。现在可以在“Styles”窗格中进行编辑:
三 UI & Code 3.0 新时代
1 自动智能化
在现在前端开发趋势越来越智能化的时代,如果用上 CSS-in-JS 在未来的无论是输出还是输入都有很大的便利性和可控性。
假如把前端和设计的协同工作分为三个时代:
- v1.0:设计资源和信息需要设计师手动额外切图说明,无法复制
- v2.0:设计资源和信息由设计文件自动化生成,可人工复制
- v3.0:设计资源和信息由设计源文件和代码自动读取,无需人工复制
也就是说,可以通过接口、SDK或插件,可以把设计文件的资源和信息读取到代码中,减少人工维护和开发的成本,建立起是设计和程序的桥,方便双方的协同工作。
现如今,很多设计软件都推出了自己的一套插件或 SDK 以供开发者使用,如下图 Sketch 插件的开发:
2 跨平台
CSS-in-JS 在跨平台的优势是比较大的,在不同的系统平台上都有 JS 的 Runtime 的实现,而且 JSON 序列化后的数据也能被更多的平台和语言消费,现在光靠纯 CSS 是无法达到这种通用性和扩展性。
四 展望未来
CSS 设计的初衷是为了全局化的控制样式,通过选择器去扩展丰富实际的页面渲染,而 CSS-in-JS 并不是排斥 CSS 样式,而是说“样式”在现代化的组件颗粒化的发展下,使用 CSS-in-JS 能在瞬息万变的复杂应用场景下更加灵活的解决更多问题。
笔者因早前开发过自己的一套 React UI 库 React-UWP,也基于这套 UI 库做了 CSS-in-JS 的方案,在过去两年中在开发中虽然用的组件不是很多,但是用了 CSS-in-JS 来做整体的样式解决方案,在组件扩展、主题自定义和状态同步有着很大的优势,也期望在后续的社区中有更多优秀的实践可以参考。
如果在文章中发现有误之处,欢迎反馈、纠正。
Links
https://www.w3.org/Style/CSS20/history.html
https://levelup.gitconnected.com/a-brief-history-of-css-in-js-how-we-got-here-and-where-were-going-ea6261c19f04
https://github.com/MicheleBertoli/css-in-js
https://zhuanlan.zhihu.com/p/165089496
https://zhuanlan.zhihu.com/p/103522819
https://zhuanlan.zhihu.com/p/59692295
https://zhuanlan.zhihu.com/p/30118092
https://medium.com/dev-channel/the-cost-of-javascript-84009f51e99e
https://juejin.cn/post/6844903808049348616
https://www.infoq.com/news/2020/04/facebook-cssinjs-react-conf-2019/
https://sebastienlorber.com/atomic-css-in-js
https://www.nonenglishengineer.com/css-design-patterns/
https://dev.to/carlillo/understanding-itcss-real-case-using-itcss-in-a-ghostcms-blog-1p9b
https://engineering.fb.com/2020/05/08/web/facebook-redesign/
https://zhuanlan.zhihu.com/p/98831543
https://www.qed42.com/blog/building-powerful-custom-properties-CSS-houdini
https://laptrinhx.com/the-future-of-css-has-come-3034181035/
https://zhuanlan.zhihu.com/p/20939640
https://www.smashingmagazine.com/2016/03/houdini-maybe-the-most-exciting-development-in-css-youve-never-heard-of/
https://developer.mozilla.org/zh-CN/docs/Archive/CSS3
http://www.airbrite.co.uk/css-training-css-specification/
https://aotu.io/notes/2019/10/29/css-preprocessor/index.html
https://zhuanlan.zhihu.com/p/36103933
https://www.w3.org/Style/CSS/current-work.en.html
https://github.com/ladjzero/ladjzero.github.io/blob/master/assets/a_brief_history_of_css.pdf
https://developer.mozilla.org/en-US/docs/Web/Houdini
https://drafts.csswg.org/css-variables-1/
https://drafts.csswg.org/css-values-4/
https://juejin.cn/post/6844904152548507661
https://engineering.fb.com/2020/05/08/web/facebook-redesign/
https://css-tricks.com/growing-popularity-atomic-css/
https://css-tricks.com/lets-define-exactly-atomic-css/
https://www.smashingmagazine.com/2013/08/other-interface-atomic-design-sass/
https://www.smashingmagazine.com/2013/10/challenging-css-best-practices-atomic-approach/
https://bradfrost.com/blog/post/atomic-web-design/
https://bradfrost.com/blog/post/atomic-design-book/