CSS(Cascading Style Sheet)

CSS的主要功能是用来指定HTML文档的样式(或许也可以应用于其他DOM文档)。

一个HTML文档可以看作一颗DOM树,由不同的DOM节点组成。这些个DOM节点代表的是HTML元素,比如<a>, <body>之类的。每个HTML元素都带有若干属性,可以用来指定HTML元素的样式。

在CSS之前,HTML元素的样式属性是直接书写在HTML元素标签上的。这种做法可扩展性和可维护性都比较差。比如,这种方式难以解决一个看似简单的问题:“如果让多个HTML元素具有相同的样式属性集合?”。 为此,很自然需要一种机制,来为多个HTML元素指定相同的样式集合。一个很自然的解决方案是给一个样式集合起一个名字,这样样式集合就成为了一个样式类(class)。一个样式类可以应用到不同的元素上。

不过样式类的到HTML元素可能会遇到一个技术问题:万一样式类中带有的样式属性在某个HTML元素上不被支持怎么办?这时候一个简单的处理办法可能是直接在该HTML元素上忽略这个样式属性。

有了样式类的概念,一个样式类可以应用到多个HTML元素,那么让一个HTML元素可以应用多个样式类也是非常自然的事情。技术上,只要遵循后来居上的原则,让后来的样式类中的属性覆盖先来的样式类中的属性,就不存在冲突问题。

在样式类的应用上,CSS还有许多绝活。比如,CSS可以通过选择器(selector)来灵活地指定样式类该应用在什么元素集合上。CSS还带有伪类(pseudo class)功能,可能用来增加更多应用的场景。比如:hover这个伪类可以增加交互式性,让鼠标移到HTML元素上的时候触发不同的样式。

作为一种样式描述语言,CSS基本上是静态的,但是也具有一部分计算能力,比如其calc()函数可以在指定样式属性值的时候进行一些计算。此外,最新版本的CSS还支持CSS Custom Properties,可以在CSS中使用类似变量的功能。

但是CSS毕竟不是一门编程语言,如果需要更大的计算灵活性,则需要一些预处理器,比如SASS,在生成CSS之前做一些预处理。

PostCSS

PostCSS是一个CSS处理工具。和前面提到的SASS不同,PostCSS并不是一个预处理器,它的处理CSS的思路有点模仿浏览器对CSS的处理,先对解析CSS样式表,并生成相应的AST(抽象语法树);不过PostCSS并不像浏览器那样直接将AST应用到HTML文档中,而是允许插件对生成的AST进行修改,最后PostCSS把修改后的AST再合成CSS样式表以备使用。

PostCSS Architecture描述了PostCSS的架构;PostCSS Plugins里面列举了许多插件。PostCSS的API可以在http://api.postcss.org/查看。

类似的工具其实不止PostCSS一家,其他的可以参考PostCSS Benchmarks中的parsers一节。

TailwindCSS

看TailwindCSS的设计,它提供了分类,螺丝刀,钉子。

安装tailwindcss cli:

yarn global add tailwindcss

独立的CLI可以通过scoop安装:scoop install tailwindcss,竟有36MB之巨。

然后就可以使用tailwindcss或者tailwind命令。

tailwind init可以创建一个配置文件tailwind.config.js

例如:

module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [
    // 示例插件
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
  ]
}

说明点:

配置可以参考https://tailwindcss.com/docs/configuration

两个相关命令:

tailwind -i input.css -o output.css --watch
tailwind -i input.css -o output.css --minify

通过-c选项可以指定配置文件,而不是使用默认的tailwind.config.js

corePlugins配置分组可以允许禁用某些核心功能:

module.exports = {
  corePlugins: {
    float: false,
    objectFit: false,
    objectPosition: false,
  }
}

上面是指定哪些核心功能要关闭,如果要指定白名单,则使用下面的方式:

module.exports = {
  corePlugins: [
    'margin',
    'padding',
    'backgroundColor',
    // ...
  ]
}

多配置的实现有几种方式。

可以在css文件中指定配置文件:

@config "./tailwind.site.config.js";

@tailwind base;
@tailwind components;
@tailwind utilities;

可以在js中编程使用:

import resolveConfig from 'tailwindcss/resolveConfig'
import tailwindConfig from './tailwind.config.js'

const fullConfig = resolveConfig(tailwindConfig)

如果在给配置加上typescript信息,可以:

/** @type {import('tailwindcss').Config} */
module.exports = {
  ...
}

观感

tailwindcss给人的感觉,有点类似于汇编之于机器码。像C/C++这种程序语言,一般先编译成汇编语言,然后再通过汇编器转化成机器码。

在CSS的语言框架内,机器码可以看出是CSS所给出的各种辖属,比如style="color: blue"这种随行的CSS,其中用到color辖属。 这样的用法缺乏扩展性,因为在不同主题、或不同场景下,需要设置不同的color,直接写死导致难以修改。 很多CSS框架的做法是指定一个CSS标类,用于描述语言,比如用一个.btn,表示一个按键,此标类下的HTML元素都具有按键的语义。

Tailwind则是在CSS辖属和用于表示语义的.btn们之间找了一个平衡,用CSS标类表示一个原子基本的语义,然后.btn可以被拆散成了多个原子语义。 原子语义的好处是它更稳定,组合起来更方便。可以用原子语义组合成目标语义。如果工程中需要经常变动和增删目标语义的话,会更方便。

原子语义也是用CSS的标类来指定的,这利用了HTML元素上可以指定多个CSS标类的特性。

原子语义的缺点是它必须足够多,足够庞杂,才能对目标语义形成足够多的支撑。这样一来,存放原子语义的CSS文件就会变得非常大。 Tailwind采用的策略是按需裁剪,也就是把没有用到的原子语义给剔除掉。2.x版本中,Tailwind使用的是PurgeCSS来裁剪,3.x版本中,Tailwind自研了JIT功能,不再依赖第三方。

我觉得这个方向应该能更进一步,原子语义还是比较适合范化和重构操作的,如果有IDE的支持的话,会如虎添翼。简单地说,IDE可以对元素上指定的原子语义CSS标类进行排序,或将其提取生成更高一级语义CSS标类。

参考

其他参考

CSS Custom Properties相关

UnoCss的概念有点类似于tailwind,但是手段有所不同。

(本篇完)

2023-01-25更新:Next.js

Get started with Tailwind CSS有针对各个流行框架的安装教程。

  • Install Tailwind CSS with Next.js
    • Create your project
      • npx create-next-app@latest my-project --typescript --eslint
      • cd my-project
    • Install Tailwind CSS
      • npm install -D tailwindcss postcss autoprefixer
      • npx tailwindcss init -p
    • Configure your template paths
      • 对tailwind.config.js做相应修改
    • Add the Tailwind directives to your CSS
      • 在globals.css中添加相应的@tailwind指示
    • 这样就可以在jsx中使用tailwind css了
      • 例子:<h1 className="text-3xl font-bold underline">

(更新完)

2023-01-26更新:DaisyUI

(更新待续)