2021了,你还没用过 tailwindcss 吗

3,723 阅读6分钟

春节将至,又一年就这样悄悄溜走了。

时值年末,总是会被各种年终总结刷屏。例如与前端开发者息息相关的state of js 2020state of css 2020调查问卷都相继发出了统计报告,在后者的报告总结中,tailwindcss可以说是异军突起,实力抢眼。

今天就来聊聊,tailwind和它的逆袭之路。

序:tailwind的逆袭之路

在 state of css 2020 的报告数据中,我们会发现 tailwind 的使用满意度非常高,同时使用过 tailwind 的开发者比例在过去的一年了飞速增长。比对积极/消极体验拆分图,tailwind 可以说是“没用过的都想试试,用过的都说好”。

与之对应的,是tailwind背后的 css 原则 atomic css 超越开发者广泛接受的 BEM 命名规则,成为开发者满意度、关注度最高的 css 原则。

why:tailwind缘何而生

tailwind 所关注的问题,可以说是前端开发领域的老大难了:全局 css 冲突。

全局 css 冲突,这一问题随着 SPA 的出现第一次受到了广泛的关注,并引发了一系列的 css 原则和工具的诞生。而随着微前端时代的到来,伴随着新的场景,这一问题再次吸引了更多的关注。

由此诞生的原则和工具包括:

  • 以 BEM 命名法为代表,旨在规范 css,减少 class name 冲突的 css 原则:BEM命名规则,ITCSS,OOCSS,atomic css……
  • 通过介入编译过程来动态修改 class name 或添加属性,以避免选择器冲突:css in js,scoped css……
  • 伴随着微前端出现的上下文隔离方案:web components,iframe……

css 原则

为了理解 tailwind 背后的理念,让我们先来简单了解下几个常见的 css 原则。

BEM 命名规则

BEM 是一种类名命名规则,出现较早,在开发者中有着很高的接受度。BEM 即block__element--modifier,通过模块作为前缀的命名空间来避免类名冲突:

.dialog__button {}
.dialog__button--confirm {
  color: green;
}

BEM作为出现很早的方案,特点是实践非常简单,甚至无需预处理器(当然一般会配合预处理的嵌套功能使用)和属性选择器(彼时甚至可能需要兼容ie6),当然不足之处也很明显:如何命名是软件工程永恒的难题之一,随着项目的增长如何命名模块来避免冲突,以及长到叹息的类名。

ITCSS

ITCSS 的理念是将 css 代码分为7个不同的层级来组织代码:

  • settings:设置,设定变量等等
  • tools:工具,定义 mixins 等等
  • generic:通用,例如 normalize 代码
  • base:基础,以标签作为选择器的 css 代码
  • object:一些抽象的通用 css 代码
  • component:组件层级的 css 代码
  • trumps:王牌,唯一允许!important的地方

例如:

// settings
$yellow: #ffaa00;
// tools
@mixin sample-mixin() {}
// generic
* { box-sizing: border-box; }
// base
p { line-height: 1.5; }
// object
.o-container { display: block; }
// component
.c-btn { cursor: pointer; }
// trumps
.u-hidden { display: none!important; }

可以将每一层的名字作为文件名的前缀,并配合@import使用:

@import 'settings.color'
@import 'settings.size'

@import 'tools.gradient'

@import 'generic.normalize'

@import "elements.headings";
@import "elements.links";

@import "objects.animations";
@import "objects.layout";
@import "objects.list";

@import "components.buttons";
@import "components.dialogs";

@import "trumps.utilities";

而在类名命名规则上,则推荐结合了 BEM 的 BEMIT。

OOCSS

OOCSS(面向对象的 CSS)则是借用了面向对象编程中类和继承的概念,并使用驼峰法来命名。例如下面的例子,抽取了metadata作为基类,并衍生出了三个继承类:

<!-- posts -->
<p class=”metadata postMetaData”>
  <a>Author name</a>commented on<a>21-02-2010</a>@
</p>
<!-- comments -->
<p class=”metadata commentMetaData”>
  <a>Author name</a>commented on<a>21-02-2010</a>@
</p>
<!-- user info -->
<p class=”metadata useInfoMetaData”>
  <a>Author name</a>commented on<a>21-02-2010</a>@
</p>

OOCSS关注的是类名的语义化,以及公用 css 提取。

Atomic css

Atomic css 提倡使用原子化的 css 类来书写代码。其理念包括:

  • 工具类(utility classes):atomic css 使用拆分到原子粒度的工具类。例如.text-right { text-align:: right },一个类只对应一条单一的 css。
  • 不变(immutable)CSS:不修改css,修改元素的类名。
  • breakpoint prefix:atomic css 支持相应式设计,通过将断点作为类名前缀来实现支持。

于是,使用 atomic css 的代码可能是这样的:

<div class="bg-grey text-white text-center grid-12 m:grid-6">
  hello atomic css
</div>

也许看到这样的理念,开发者难免会冒出“这是neng啥呢?”的想法。正是这种反直觉的设计理念,以及“用过都说好”的使用体验,结合当下前端的发展趋势,使得 atomic css 以及其理念下的优秀产物 tailwind 在过去的一段时间内迎风起飞,成为前端领域的耀眼明星。

what:tailwind 的理念

工具类优先

tailwind 与相比传统的 css 框架相比,后者通常会抽取公共组件样式,为开发提速的同时,也带来了一定的束缚,而前者则通过工具类的自由组合提供了更大的自由度。

同时,将 css 规则拆分到原子的粒度后,只要事先准备好一套工具类,那么在开发过程中,无论是SPA还是微前端,就不必修改也不必增加新的 css 代码,也不用担心 css 冲突了。

响应式设计

tailwind 支持响应式设计,默认包含四个屏幕宽度断点:

/* Small (sm) */
@media (min-width: 640px) { /* ... */ }

/* Medium (md) */
@media (min-width: 768px) { /* ... */ }

/* Large (lg) */
@media (min-width: 1024px) { /* ... */ }

/* Extra Large (xl) */
@media (min-width: 1280px) { /* ... */ }

tailwind 提倡移动优先的设计理念:

<div class="text-center md:text-left"></div>

组件友好,可定制、可扩展

除了直接使用工具类之外,tailwind 还支持在工具类的基础上抽提可复用的组件。通过其插件化的机制,可以将组件、自定义工具类抽取为 js 插件,在项目间共享。

tailwind 的主题同样是可修改、可扩展的。

how:使用 tailwind

说了这么多,不知道你是否已经跃跃欲试了。tailwind 官网上的教程非常细致,跟着它来了解 tailwind 的方方面面几乎不会遇到任何阻碍。当然,如果你想先对 tailwind 有个初步了解,也可以先看看以下的极简上手指南。

安装使用

这里以 webpack + postcss 为例,当然 tailwind 也可以与其他打包工具及 css 预处理器一起使用,具体可以参见官网。

(目前的 tailwind 需要与 postcss 版本8 一起使用,如果你是通过vue-cli或是create-react-app等工具来创建的项目,或许需要先升级一下 postcss 的版本。)

安装:

yarn add tailwindcss

随后,创建一个index.css文件:

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

通过命令或手动创建tailwind.config.js

npx tailwindcss init

配置 postcss-loader:

rules: [
  {
    test: /\.css$/i,
    use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
  },
  ...
]

配置postcss.config.js文件:

module.exports = {
  plugins: [
    [
      "postcss-preset-env",
      {},
    ],
    require('tailwindcss'),
    require('autoprefixer'),
    isProductionMode && require('cssnano')(),
  ].filter(Boolean),
};

tailwind 会生成大量的工具类,但是我们通常之后使用其中的一小部分,因此生产环境需要配合PurgeCss移除没用到的类。

编辑tailwind.config.js

module.exports = {
  purge: {
    enabled: isProductionMode,
    content: ['./src/**/*.jsx'],
  },
};

preflight

tailwind 在normalize.css的基础上建立了基础样式,称为 preflight(预飞、预检,源自飞行器起飞前的检查工作),通过base引入:

// 芜湖,起飞
@import "tailwindcss/base";

工具类

tailwind 的工具类涵盖了大部分的常规需求,官网上有详细的列表:

  • 布局
  • 弹性盒子
  • 网格布局
  • 空间
  • 尺寸
  • 排版(typology)
  • 背景
  • 边框
  • 表格
  • 特效
  • 过渡
  • 形变
  • 交互
  • svg
  • 可访问性

熟悉这些工具类也许会花上不少时间,不过当你熟悉了 tailwind 的使用,相信你会乐在其中。

// 也许你需要一些时间来习惯长长的类名
return (
  <div className="bg-yellow-300 text-white text-center p-2 mt-4">
    淡黄的背景,雪白的文字
  </div>
);

编辑器插件

为了方便使用,编辑器插件是必不可少的,如果你在使用 vscode,那么 tailwind css intellisense 便是一个不错的选择:

  • 自动补全
  • lint
  • 悬停预览:当鼠标悬停在类名上时,展示其对应的 css

例如:

.text-white {
  --tw-text-opacity: 1;
  color: rgba(255, 255, 255, var(--tw-text-opacity));
}

.mt-4 {
  margin-top: 1rem/* 16px */;
}

响应式

tailwind 预设了四个屏幕宽度断点:

  • sm: min-width: 640px
  • md: min-width: 768px
  • lg: min-width: 1024px
  • xl: min-width: 1280px

使用时,通过[断点]:[工具类]的方式设置不同响应式的样式。建议遵从移动优先的理念,即先设置基础样式,在用大屏下的样式去覆盖,例如:

<div class="w-16 md:w-32 lg:w-48">mobile first</div>

伪类(variants)

伪类同样通过[伪类]:[工具类]的方式使用,例如:

class="bg-teal-500 hover:bg-teal-600 focus:outline-none"
// 同时设置响应式和伪类:响应式:伪类:样式
class="sm:hover:bg-green-600"

伪类包括:

  • hover
  • focus
  • active
  • disabled
  • visited
  • child
  • last
  • odd
  • even
  • group & group-hover
  • group & group-focus
  • focus-within

组件提取

tailwind 提供了@apply,以实现组件的提取功能:

// jsx
return <div className="btn btn--blue">btn</div>

// index.css
.btn {
  @apply font-bold py-2 px-4 rounded;
}
.btn-blue {
  @apply bg-blue-500 text-white;
}
.btn-blue:hover {
  @apply bg-blue-700;
}

为自定义 css 生成前缀

tailwind 提供了@responsive以及@variants来为我们自己编写的 css 生成前缀:

return (
  <>
    <div className="xrotate-0 hover:xrotate-90"</div>
    <div className="xrotate-0 md:xrotate-90"</div>
    <div className="xrotate-0 md:hover:xrotate-90"</div>
  </>
);

@responsive {
  @variants hover, focus {
    .xrotate-0 {
      transform: rotate(0deg);
    }
    .xrotate-90 {
      transform: rotate(90deg);
    }
    .xrotate-180 {
      transform: rotate(180deg);
    }
    .xrotate-270 {
      transform: rotate(270deg);
    }
  }
}

css 文件结构

tailwind 建议以一定的顺序在 css 文件中引入 tailwind、定义全局样式、抽取组件、编写自定义工具类:

// 引入基础库
@tailwind base;

// 自定义全局样式
h1 {
  @apply text-2xl;
}
a {
  @apply text-blue-600 underline;
}
// 字体
@font-face {
  font-family: Proxima Nova;
  font-weight: 400;
  src: url(/fonts/proxima-nova/400-regular.woff) format("woff");
}

//
@tailwind components;

// 自定义组件
.btn {
  @apply font-bold py-2 px-4 rounded;
}
.btn-blue {
  @apply bg-blue-500 text-white;
}

//
@tailwind utilities;

// 自定义工具类
@responsive { /* 可选 */
  @variants hover, focus { /* 可选 */
    .xrotate-0 {
      transform: rotate(0deg);
    }
  }
}

配置文件与主题

配置文件允许我们编辑和扩展主题、修改伪类的生成规则、引入插件等等:

module.exports = {
  theme: {},
  variants: {},
  plugins: [],
}

关于主题的内容,大家可以参见官方文档,这里先不介绍了

插件机制

tailwind 的插件机制,提供了可复用的扩展组件和工具类的方式:

// tailwind.config.js
module.exports = {
  theme: {
    // ...
    // 使用 gradients 插件
    gradients: theme => ({
      'blue-green': [theme('colors.blue.500'), theme('colors.green.500')],
      'purple-blue': [theme('colors.purple.500'), theme('colors.blue.500')],
    }),
  },
  variants: {
    // ...
  },
  plugins: [
    // 注册插件
    require('./twplugins/utilities/gradients'),
    require('./twplugins/comps/compA')
  ],
};

// twplugins/utilities/gradients.js
module.exports = plugin(({ addUtilities, e, theme, variants }) => {
  const gradients = theme('gradients', {});
  const gradientVariants = variants('gradients', []);

  const utilities = _.map(gradients, ([start, end], name) => ({
    [`.bg-gradient-${e(name)}`]: {
      backgroundImage: `linear-gradient(to right, ${start}, ${end})`
    }
  }));

  addUtilities(utilities, gradientVariants);
});

// twplugins/comps/compA.js
module.exports = plugin(({ addComponents, config }) => {
  const comps = {
    '.comp-a': {
      padding: '.5rem 1rem',
      borderRadius: '.25rem',
      fontWeight: '600',
    },
    '.comp-a-primary': {
      backgroundColor: config('theme.colors.primary'),
      color: '#fff',
    },
    '.comp-a-secondary': {
      backgroundColor: config('theme.colors.secondary'),
      color: '#fff',
    },
  }
  
  addComponents(comps);
});

最后

看了以上介绍,你心动了没?快开始使用tailwindcss开发你的前端项目吧~