当前,前端开发关于 CSS 的技术有很多:

  • CSS Preprocessors (CSS 预处理器): Sass,Less,Stylus,PostCSS 等

  • CSS Framework: Bootstrap,Tailwind 等

  • CSS-in-JS: Emotion, Styled components

  • Atomic CSS

本文尝试对这些技术做一个简单介绍。

CSS Preprocessor

A CSS preprocessor is a program that lets you generate CSS from the preprocessor's own unique syntax.

CSS 预处理器的目的是扩展一些 CSS 不具备的功能,例如,CSS Variable, mixins, nesting selector(嵌套选择器), inheritancs selector(选择器继 承) 等。

CSS Preprocessor 有自己的语法, 需要安装 CSS compiler, 去编译出纯 CSS。

纵观常见的几个 preprocessor, 做的事情都大同小异,区别在于:

  • 语法不同

  • 支持功能的多寡

  • 依赖的编译平台

选择上,主要还是看需要什么功能,是否便于集成到当前项目中,库是否持续维护,社区大小等。

另外,如果只是变量,简单的计算,其实 CSS 本身也具有一定功能,未必要用 preprocessor。

SCSS / SASS (Syntactically Awesome Style Sheets)

  • 支持函数

  • 用 Ruby 写的,可能需要装 Ruby 相关环境,有时环境安装会有问题,碰 到过几次安装 node-sass 出错的情况

  • SCSS 相比于 SASS,写法更接近原生的 CSS,需要写大括号,分号等

Less (Leaner Stylesheets)

  • 使用 JS 编译

  • 比较容易添加到已有项目中

Stylus

  • 使用 JS 编译

  • 内置了一些处理函数,能够处理繁重的计算

PostCSS

PostCSS is a JavaScript library that transforms CSS into JavaScript.

PostCSS 和上面三者区别比较大,它会先编译出 AST,以 JS 对象形式表示, 然后可以基于 AST 做很多事情,可以自己写插件去扩展,最后再编译回浏览 器识别的 CSS。

因此,PostCSS 除了本身这个库外,还有相关的插件生态圈,可以通过插件 去实现类似 SCSS 的功能。

相对于上面三者,扩展性更好,可以实现更多功能。

CSS Frameworks

所谓的 CSS 框架,就是别人帮你把一些基础的,通用的样式写好,你直接拿 来用即可,类比组件库。

当需要扩展这些框架时,可能还需要用到 preprocessor。

框架的目的,主要是减少你需要写的 CSS,选择上可以考虑:

  • 框架体积

  • 预定义的样式是否够用, 是否符合设计的要求

  • 可扩展性,是否容易扩展,容易维护

  • 社区活跃度

Bootstrap

Powerful, extensible, and feature-packed frontend toolkit. Build and customize with Sass, utilize prebuilt grid system and components, and bring projects to life with powerful JavaScript plugins.

基于 Sass 打造的一套 CSS 框架,也支持 Less。Bootstrap 本身还提供了一些组件。

Bulma

Bulma is a free, open source framework that provides ready-to-use frontend components that you can easily combine to build responsive web interfaces.

也是基于 Sass 的框架,更轻量,基于 Flexbox。

模块化的组织结构,你可以只引用某个模块,例如按钮。

Tailwind CSS

A utility-first CSS framework packed with classes like flex, pt-4, text-center and rotate-90 that can be composed to build any design, directly in your markup.

它实际上是一个 PostCSS 插件。

高度可定制化,通过 tailwind.config.js 文件进行定制。

相比于 Bootstrap 和 Bulma,它们都是将 CSS 模板做好提供给你,而 Tailwind 更像是提供你制作自己模板的工具。

CSS-in-JS

preprocessor 的目的是为了扩展 CSS,增加一些功能特性;

CSS 框架则是预定义一套规则,不用从零开始构建 UI,便于快速开发;

那么 CSS-in-JS 是为了解决什么问题呢?

  1. CSS 作用域问题,需要担心重名,覆盖,对于这个问题,目前有一些方案:

    • 使用如 BEM 的命名规则,避免冲突,但还是可能会冲突

    • Vue 里可以给 style 添加 Scoped,限定作用域

    • Tailwind 的 CSS 框架,由于定义了许多原子化的类名,不太需要自 己定义类名写样式,也可以很大程度避免这个问题

    • CSS-in-JS,将生成的样式限定在作用的元素上,例如 Emotion,会生成 一个独特的类名(基本是唯一的)

  2. 集中定义 CSS JS HTML,将它们都放在一起,这样可以很方便的找到 CSS 去修改,也方便整体移动组件

  3. 可以使用 JS 去写 CSS,条件语句,循环,变量等,更容易地根据状态值切换样式

  4. 自动添加浏览器样式前缀

CSS modules

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default

CSS Modules let you write styles in CSS files but consume them as JavaScript objects for additional processing and safety.

CSS Modules are very popular because they automatically make class and animation names unique so you don’t have to worry about selector name collisions.

就如名字一样,CSS modules 把 CSS 看作一个模块引入使用。

它需要把 CSS 定义到一个 css 文件中,然后引入这个文件,文件中的类名 会被处理成带作用域的。

引入后,相当于一个 JS 对象,通过类似 xxxStyle.title 的形式,引用对 应的样式。

最终会生成 .css 文件,可以预先加载,缓存。

除了全局作用域的写法特别,其它和正常写 CSS 没什么差别。

1
2
3
4
5
  /* container.module.css */
  .container {
    margin: 3rem auto;
    max-width: 600px;
  }
1
2
3
4
5
6
7
  import React from "react"
  import * as containerStyles from "./container.module.css"
  export default function Container({ children }) {
    return (
      <section className={containerStyles.container}>{children}</section>
    )
  }

styled components

Styled component 会生成一个带有样式的组件。

你可以用语义更好的名字作为组件,例如写个 Title 组件,替换原来的 <h1 style="…" className="…">, 语义化更好。

由于样式和组件捆绑,所以要删的时候很容易删,不用怎么担心会不会影响其它。

不能生成 .css 文件,因此无法提前加载,无法缓存 CSS。

可以根据组件上的 props,决定组件怎么渲染。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
  const Button = styled.a`
    ${props => props.primary && css`
      background: white;
      color: black;
    `}
  `
  render(
    <div>
      <Button
        href="https://github.com/styled-components/styled-components"
        target="_blank"
        rel="noopener"
        primary
      >
        GitHub
      </Button>

      <Button as={Link} href="/docs">
        Documentation
      </Button>
    </div>
  )

Emotion

Emotion 通过引入 @emotion/styled 也可以支持 Styled components 的写法。

也可以不用 styled 写法,而是定义一个个 CSS 然后组合起来。

可以通过用字符串定义 const textColor = css`color: red;` 或者对象形 式定义 const textColor = css({ color: "red" }) 最后会返回一个 className。

如果不用 styled 方式定义,就不用通过 prop 来获取状态,可以直接访问 外部变量。

总体上,感觉 Emotion 和 Styled component 都是不错的 CSS-in-JS 技术, Emotion 语法看起来更灵活一些。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  import { css, cx } from '@emotion/css'

  const color = 'white'

  render(
    <div
      className={css`
        padding: 32px;
        background-color: hotpink;
        font-size: 24px;
        border-radius: 4px;
        &:hover {
          color: ${color};
        }
      `}
    >
      Hover to change color.
    </div>
  )

Atomic CSS

Atomic CSS is the approach to CSS architecture that favors small, single-purpose classes with names based on visual function

原子化 CSS 是一种 CSS 的架构方式,它倾向于小巧且用途单一的 class,并且 会以视觉效果进行命名。

按照定义,其实 Bootstrap, Chakara UI, Windi CSS, Tailwind, UnoCSS 这些 CSS 框架或者引擎,都算是原子化 CSS。

他们会定义一些用途单一的 class, 例如 .mr (margin-right), .mr-2, .mt (margin-top), .mt-5。一般而言,一个 class 就对应一个 CSS 属性。

1
2
3
4
5
6
7
  .mt-5 {
      margin-top: 5px;
  }

  .pt-10 {
      padding-top: 10px;
  }

原子化 CSS 定义得这么细小单一,在刚开始的时候,也许需要定义很多原子化 CSS 以满足使用的需求,到后面当定义的 CSS 满足了大部分的需求时,就不太 需要再定义了。

它的增长曲线是一条对数曲线,随着定义的原子化 CSS 越来越多,会渐渐趋于 平稳。

https://miro.medium.com/max/700/1*XYBs0ZTnU5_RIO0BT4qodg.png

此外,你不应该去改变定义好的属性值,当你需要一个不同的属性值,则定义新的类名。

定义原子化 CSS 多少有些麻烦,但目前存在很多工具,可以根据一些规则自动 生成对应的 CSS,例如 UnoCSS

1
2
3
4
  rules: [
    [/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
    [/^p-(\d+)$/, match => ({ padding: `${match[1] / 4}rem` })],
  ]

原子化 CSS 的好处是,一个样式由许多单一的 class 组成,class 对应的内容 是不变的,可以很容易地知道一个样式是有哪些属性组成的,不用再去翻对应的 CSS 定义。当需要添加样式时,也可以很容易地想到对应的类名,减少了心智负 担。

而且一些工具会按需生成 CSS,只生成你需要的 CSS,减少了 CSS 的大小。

实际使用了一段时间的 UnoCSS,用下来觉得挺流畅的,开发体验不错。

写的时候就通过加类名去定义元素的样式,大多数情况也不会写很多的类名。

当需要调整时,直接就调整元素的类名,不用再去根据类名找到样式的定义,再去改样式,nice。

通过类名也比较直观地知道元素的样式,推荐在一些 UI 不是很复杂的场景尝试一下~