给类命名太痛苦?试试tailwindcss吧

3,173 阅读9分钟

借用网上一个段子开头

面试官灵魂拷问:你是如何优雅的管理css的?

答:先写类名,在写样式内容

卒,享年18

Tailwind CSS 能帮你

借助tailwindcss官网的定义:

  • Tailwind CSS 是一个功能类优先的 CSS 框架,它集成了诸如 flex, pt-4, text-center 和 rotate-90 这样的的类,它们能直接在脚本标记语言中组合起来,构建出任何设计。
  • 无需离开您的HTML,即可快速建立现代网站

中文站点 / 英文站点

先来说说,css的进化历史

由于原生css存在许多痛点

  • 全局污染
  • 命名混乱
  • 难以复用
  • 维护性差
  • 冗余代码
  • 兼容问题

在 CSS 的进化历史上,出现过各种各样的框架或者手段致力于解决以上的问题

SASS, LESS,Stylus 等 CSS 预处理器

优点

  • 提供了变量,函数,运算,继承等。扩展性,复用性都有了很大的提升
  • 支持选择器嵌套
  • 解决了一些样式重用冗余的问题

不足

  • 对于命名混乱和全局污染问题的效果不大
  • 嵌套层级过深则不容易阅读

BEM (.block__element–modifier) 规范

优点

  • 比较流行的 class 命名规则,部分解决了命名混乱的问题

不足

  • className 定义起来冗长
  • 命名压力大,容易现很多无效的命名

CSS Modules

优点

  • 模块化 CSS,将依赖的 CSS 文件以模块的形式注入到 JavaScript 里,基本上解决了全局污染、命名混乱、样式重用和冗余的问题

不足

  • 与组件库难以配合,需要使用:global
  • 会带来一些使用成本,本地样式父子组件覆盖困难,需要山路18弯
  • 骆驼式命名的写法很蛋疼
  • 丢失了 css 的灵活性

CSS in JS

// style-components
const Button = styled.a`
  /* This renders the buttons above... Edit me! */
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
  background: transparent;
  color: white;
  border: 2px solid white;

  /* The GitHub button is a primary button
   * edit this to target it specifically! */
  ${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>
)
const Button = styled.a`
  /* This renders the buttons above... Edit me! */
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
  background: transparent;
  color: white;
  border: 2px solid white;

  /* The GitHub button is a primary button
   * edit this to target it specifically! */
  ${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>
)

优点

  • 组件化,方便开发维护和测试,JavaScript 和 CSS 可以方便的共享变量和方法
  • 基于状态的样式定义,非常适合需要动态样式的组件开发
  • 保留了原生css的写法,同时支持嵌套,js变量等
  • 可以跨平台

不足

  • ide支持较差
  • 陡峭的学习曲线
  • 降低代码可读性
  • 无法提取静态css文件
  • 运行时性能损耗

以上几种css技术解决方案没有好坏之分,不同的技术方案适用于不同的应用场景,大家在选型的时候需结合实际使用场景

前端css颗粒度划分

<div style="{ borderRadius: '0.5rem', padding: '1rem' }">Click</div>

<div class="rounded-lg p-4">Click</div>

<div class="button">Click</div>

<Button>Click</Button>

搬运知乎回答 山月

越往下走,颗粒度越来越大,约束性变高,自由性不足。而 tailwindcss 位于第二层

核型理念

tailwindcss总共分为三大块

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

@tailwind base;

基础(或全局)样式,可为诸如<a> 标签、标题等基本 HTML 元素设置有用的默认值,或者有目的重置,像流行的 box-sizing reset

如果你的项目中已经引入了默认样式,例如normalize.css,那么这部分基础样式包可以选择不引入,或者通过tailwind.config.js中配置preflight: false来关闭

// tailwind.config.js
module.exports = {
  // ... other config
  corePlugins: {
    preflight: false
  },
}

扩充你的基础类

@layer base {
  @font-face {
    font-family: NumberFont;
    src: url(https://hgkcdn.oss-cn-shanghai.aliyuncs.com/test/NumberMedium.2736700f.ttf);
  }

  @variants hover, focus {
    .font-number {
      font-family: NumberFont;
    }
  }
}

@tailwind components;

tailwind定义的一些组件类,目前只有一个container类,与bootstrap的container类作用一样。

扩充你的组件类

@layer components {
  .btn-blue {
    @apply py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75;
  }
}

@tailwind utilities;

这个是tailwind的核心,引入了这个tailwindcss才会去生成对应的属性类。

扩充你的功能类

@layer utilities {
  .scroll-snap-none {
    scroll-snap-type: none;
  }
  .scroll-snap-x {
    scroll-snap-type: x;
  }
  .scroll-snap-y {
    scroll-snap-type: y;
  }
}
tailwind 的工具类涵盖了大部分的常规需求,官网上有详细的列表:
  • 布局
  • 弹性布局
  • 网格布局
  • 盒对齐
  • 间距
  • 尺寸
  • 排版
  • 背景
  • 边框
  • 特效
  • 表哥
  • 过渡和动画
  • 转换
  • 交互
  • svg
  • 可访问性
1. 原子化 CSS (Atomic CSS)

TailwindCss 将类名拆到了最小的单位,我们只需要用到一定数量的原子类,就能完成一个复杂的页面样式。这也是为啥使用 TailwindCss 的 tree-shake 后,压缩后的 css 体积可以降低到 10kb 以下的原因。

<!-- Using utilities -->
<button class="py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-green-500 hover:bg-green-700">
  Click me
</button>

<!-- Extracting classes using @apply -->
<button class="btn btn-green">
  Button
</button>

<style>
  .btn {
    @apply py-2 px-4 font-semibold rounded-lg shadow-md;
  }
  .btn-green {
    @apply text-white bg-green-500 hover:bg-green-700;
  }
</style>
2. 响应式设计

TailwindCss 中的每个功能类都可以有条件的应用于不同的断点(breakpoints),在不同分辨率设备上,可以轻松切换属性。内联样式中,无法使用媒体查询。

根据常用的设备分辨率方案,默认内置了 5 个断点:

image.png

要添加一个仅在特定断点生效的功能类,只需要在该功能类前加上断点名称,后面跟 : 字符。

<!-- Width of 16 by default, 32 on medium screens, and 48 on large screens -->
<img class="w-16 md:w-32 lg:w-48" src="...">
3. 伪类

与 TailwindCss 处理 响应式设计 类似,通过为功能类添加适当的状态变体前缀,可以对处于 hover 、focus 和其它状态的元素设置样式, 而内联样式无法设置 hover 或者 focus 这样的状态。

<button class="bg-red-500 hover:bg-red-700 ...">
  Hover me
</button>
4. 函数与指令
  • @tailwind 指令将 Tailwind 的预设基础样式导入到项目的样式表中
  • @apply 指令将行内存在的(共用)基础类提取出来放到项目的 CSS 样式表中,「汇总」为一个类,然后在 HTML 元素就可以只写这个类名就应用其包含的所有基础类
  • @layer 指令告诉 Tailwind 自定义样式属于哪个 bucket,通过该指令可以直接在项目的样式表 styles.css 定制基础类、伪类变量等,但还是推荐在配置文件 tailwind.config.js 中进行设置
  • @variants 指令可以在样式表中使用或定制伪类变量
  • @responsive指令可以在样式表中使用断点
  • @screen 指令可以在样式表中使用屏幕相关的媒体查询,设置响应式样式
  • theme() 函数可以在样式表中读取 Tailwind 配置文件的值

原理

没有任何黑魔法,仅仅是排列组合而已 (no magic just permutationCombination)

/* Input */
@variants hover, focus {
  .banana {
    color: yellow;
  }
}

/* Output */
.banana {
  color: yellow;
}
.hover\:banana:hover {
  color: yellow;
}
.focus\:banana:focus {
  color: yellow;
}

例如声明一个banana,并且应用hoverfocus伪类,那么通过@variants并追加hover, focus,tailwind编译器便会通过排列组合生成响应的css

说实话在未使用tailwindcss之前也通过lessscss干过类似的事情,例如通过less的函数循环生成对应的类名

// input
.genGrid(@n, @i: 1) when (@i =< @n) {
  .grid@{i} {
    width: (@i * 100% / @n);
  }
  .genGrid(@n, (@i + 1));
}
 
.genGrid(5);

// output
.grid1 {
  width: 20%;
}
.grid2 {
  width: 40%;
}
.grid3 {
  width: 60%;
}
.grid4 {
  width: 80%;
}
.grid5 {
  width: 100%;
}

性能优化

tailwindcss的原理是排列组合,所以组合越多,最终生成的css文件越大,使用默认配置,TailwindCSS 的开发版本是3739.4kB未压缩,293.9kB用Gzip进行压缩,73.2kB用Brotli进行压缩。,所以我们需要结合主流构建工具,例如webpack,来剔除我们未使用到的类及其内容。

image.png

配置十分简单

// tailwind.config.js
module.exports = {
  // ...otherConfig
  purge: [
    './src/**/*.html',
    './src/**/*.vue',
    './src/**/*.jsx',
  ],
}

其原理就是通过正则表达式扫描指定的文件列表,将未使用到的类剔除,效果类似tree shaking

所以在写类名的时候最好写全

❌ bad

<div class="text-{{ error ? 'red' : 'green' }}-600"></div>

✅ good

<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>

安装配置

npm install tailwindcss@latest postcss@latest autoprefixer@latest

注意需要PostCSS 8且Node版本 > 12.13

如果是postcss7,那么执行一下两个命令,安装postcss&兼容版本

npm uninstall tailwindcss postcss autoprefixer
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

创建一个 tailwind.css 文件

@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

通过指令创建tailwind.config.js

npx tailwindcss init
// tailwind.config.js
module.exports = {
  purge: [
    './src/**/*.html',
    './src/**/*.vue',
    './src/**/*.jsx',
  ],
  theme: {},
  variants: {},
  plugins: [],
}

配置postcss.config.js

// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

默认情况下tailwindcss的间距都是使用rem为标准的,你也可以自定义theme来将其改成px,以下是我在项目中使用的配置

module.exports = {
  // ... other config
  theme: {
    fontSize: {
      xs: '12px',
      sm: '14px',
      base: '16px',
      lg: '18px',
      xl: '20px',
      '2xl': '24px',
      '3xl': '30px',
      '4xl': '36px',
      '5xl': '48px',
      '6xl': '60px',
      '7xl': '72px',
    },
    spacing: {
      px: '1px',
      0: '0',
      0.5: '2px',
      1: '4px',
      1.5: '6px',
      2: '8px',
      2.5: '10px',
      3: '12px',
      3.5: '14px',
      4: '16px',
      5: '20px',
      6: '24px',
      7: '28px',
      8: '32px',
      9: '36px',
      10: '40px',
      11: '44px',
      12: '48px',
      14: '56px',
      16: '64px',
      20: '80px',
      24: '96px',
      28: '112px',
      32: '128px',
      36: '144px',
      40: '160px',
      44: '176px',
      48: '192px',
      52: '208px',
      56: '224px',
      60: '240px',
      64: '256px',
      72: '288px',
      80: '320px',
      96: '384px',
    },
    extend: {
      lineHeight: {
        3: '12px',
        4: '16px',
        5: '20px',
        6: '24px',
        7: '28px',
        8: '32px',
        9: '36px',
        10: '40px',
      },
    },
  },
}

tailwind v1.x 升级 v2.x注意点

  1. postcss版本需要升到8,或者安装兼容版本tailwindcss@npm:@tailwindcss/postcss7-compat
  2. Node版本需要升到12.13.0以上
  3. 如果升级了postcss注意升级对应的postcss 插件包

vs code编辑器插件

支持智能提示,语法高亮,鼠标hover显示具体类内容等,如果发现不生效,请升级vs code编辑器版本

实用工具

配置中可能存在的问题

1. csslessscss样式文件中stylelint提示unknownAtRules,
// .stylelintrc.js
{
  "rules": {
    'at-rule-no-unknown': [
      true,
      {
        ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen'],
      },
    ],
  }
}
2. Vue项目中,sfc文件,在vscode中提示 Unknown at rule @apply scss(unknownAtRules)警告
// setting.json
{
    "css.validate": false,
    "less.validate": false,
    "scss.validate": false,
    "css.lint.unknownAtRules": "ignore",
}

优势

  • 提高了开发效率,对于一个简单的样式需求,例如间距,不用在自己定义一个class,直接使用mt-1这样即可
  • 不用在为给类命名而浪费精力
  • CSS 体积不会因为项目的迭代而增长
  • 更改会更安全,不用担心css全局性带来的样式混乱
  • 自带专业的ui设计风格,不会写出花花绿绿的页面,整体视觉效果统一,包括间距、色彩、字体等等

不足

  • class名称过长,影响阅读 真香!!! 使用了tailwindcss并不意味这你所有样式都需要写在html里,你也可以通过BEM规范命名元素,并通过@apply组合样式,当然你也可以自己扩展tailwindcss,提高复用性。

总结

起码你再也不用为命名烦恼,对于简单一些css样式,也不再需要命名一个class或者写到style

虽然但是,所有的技术方案都需要结合实际使用场景,否则将变得毫无意义,例如css in jscss module方案,在开发一些公共组件库的时候并不是那么合适,这个时候BEM + sass/less或许更加合适,因为前者为解决css作用域问题都会给class加上一个随机字符串,这对用户想要自定义样式等操作来说并不友好。

参考文章