跳到主要内容

Tailwind CSS

Tailwind 是什么?

官网:https://tailwindcss.com/

中文:https://tailwind.nodejs.cn/

Tailwind CSS1 是一个实用的工具集,它允许你通过预定义的类来快速构建和定制界面样式,而不是创建传统的CSS样式表。它遵循原子设计原则,提供了大量可组合的小型样式类。

Tailwind npm trends2

Tailwind CSS 优点3

  • 可定制化程度极高
  • 不需要再写 css
  • 对于类名的规范统一:不需要再为 class 取个什么名字而苦恼
  • 响应式设计
  • 一套专业的 UI 属性值

安装、快速上手

安装 Tailwind 及其依赖项 postcssautoprefixer

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

初始化配置文件:

# 执行以下命令会创建tailwind.config.js和postcss.config.js文件
# 带上`-p`参数表示同时生成postcss配置文件
npx tailwindcss init -p

生成的 tailwind.config.js 文件内容如下:

/** @type {import('tailwindcss').Config} */
module.exports = {
content: [],
theme: {
extend: {},
},
plugins: [],
}

生成的 postcss.config.js 文件内容如下:

module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Hello World 示例参考官方 Get Started:https://tailwindcss.com/docs/installation

learn-tailwind
├── node_modules
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
├── index.html
├── input.css
└── output.css
└── tailwind.config.js

提示:如果有波浪线提醒,可以在 vscode 安装下插件:PostCSS Language Support

在 HTML 中使用

在 HTML 中直接使用 Tailwind CSS 的类,例如 <div class="bg-gray-300 p-4 rounded">。 熟悉基础布局和定位类,如 flex, grid, justify-center, items-start 等。 学习响应式设计类,如sm: (small), md: (medium), lg: (large) 用于不同屏幕尺寸。

TailwindCSS 最基本的使用方式,就是和之前 bootstarp 等 CSS 库用法没什么两样,就是 class 上加各种类名。TailwindCSS 的类名都是由 属性缩写 + 属性程度 + [属性值]。比如在默认情况下,TailwindCSS 对于数值分成几个程度,每个程度为 0.25rem。如: mt-2 表示 margin-top: 0.5rem 。对于 opacity 等属性还有属性值,如bg-opacity-30 表示 --tw-bg-opacity: 0.3; // e.g. background-color: rgba(0, 0, 0, var(--tw-bg-opacity));。(对于颜色、变换等,都会使用变量。)还提供了一些字面量,如 md sm 等用于响应式布局。

记不住怎么办?—— 可以使用 VSCode 插件:Tailwind CSS IntelliSense4

类名堆的太多怎么办? —— 可以使用 @apply 语法包起来(通过 PostCSS 预处理)

.test { 
@apply relative w-full h-[40px] bg-background-regular flex items-center justify-between px-4 truncate;
}

注:需要安装 PostCSS Language Server 才能使用针对于 PostCSS 的补全。PostCSS 也是一种格式,扩展名为 .css .pcss

Tailwind CSS 设计系统

参考:https://juejin.cn/post/6951300894684577823

Tailwind 的开箱即用主要体现在:

  • 精心设计的 design token
  • 支持响应式
  • 支持伪类等 CSS 状态
  • 支持 Dark 模式

“Tailwind CSS 的扩展机制,保证了样式层的规范都来源于同一个地方——Tailwind。”

在项目中使用

打包构建

由于 Tailwind 需要把所有的 css 属性全部都封装了一遍,所以 css 文件巨大,3M多,所以不建议在页面你及直接引入 Tailwind 原生 css 文件的做法。

为了解决这个问题,可以在编译时引入 PurgeCSS5,构建时就会自动删掉所有没用到的 css,只保留你用到的 css,这样最终打包出来的 css 文件就非常小了。

CSS in JS

对于喜欢「CSS in JS」写法的开发人员,可以借助 twin.macro 这个库,在「CSS in JS」中引用 Tailwind CSS。这样就可以既享受「CSS in JS」的优势,又能保证 Tailwind CSS 作为样式层的 Single Source Of Truth。下面是一个示例:

import 'twin.macro'

const Input = () => <input tw="border hover:border-black" />

在 Taro 中使用

参考:使用 Tailwind CSS | Taro 文档

weapp-tw

把tailwindcss带给小程序开发者们 用tailwindcss来开发小程序吧!这是一个webpack/vite/gulp插件集合,兼容了各种用这类打包的框架,比如uni-app,tarojs,rax,mpx,remax,原生等等.伟大的icebreaker部署了这个文档网站 | weapp-tw 把tailwindcss带给小程序开发者们

如何保证原子化的样式类名称有一个比较合理的顺序

tailwindcss提供了一个 prettier 插件:prettier-plugin-tailwindcss

生产优化

参考:https://www.w3cschool.cn/tailwind_css/tailwind_css-vcbz3p8

PostCSS 是什么东西?

前端 - PostCSS 是个什么鬼东西? - 60sky - SegmentFault 思否

“PostCSS 可以直观的理解为:它就是一个平台、平台、平台!”

嵌套语法

tailwind 插件:@tailwindcss/nesting

固定行数后截断

tailwind 插件:@tailwindcss/line-clamp

移动端适配方案

todo:了解 css 的各个单位

可能对于移动端适配,现在流行的就是viewport方案了,也可能有的项目还在用flexable方案,但是我们又不想手动换算pxremvw,虽然社区也有类似pxtorem或者pxtovw这种postcss的插件,但解决问题的方法还是不够优雅,可能是因为插件的维护的不积极,可能是插件不好用,不兼容postcss8(pxtovw说的就是你😤),既然我们都有tailwindcss了,那就让这些配置变的更简单一些吧!如果你们设计同学提供了常用的间距方案,比如4px的倍数或者6px的倍数,现在假设设计同学的设计稿都是750px的,我们就可以基于此来写两个函数方法来处理pxtorempxtovw的任务,如果你们是flexable方案就用pxToRem,如果是viewport的适配方案就用pxToVmin

function pxToRem(variable) {
return `${variable / 75}rem`
}

function pxToVmin(variable) {
return `${variable / 7.5}vmin`
}
// flexable
const fontSize = {
12: pxToRem(12),
14: pxToRem(14),
16: pxToRem(16),
...
}, spacing = {
0: 0,
4: pxToRem(4),
8: pxToRem(8),
12: pxToRem(12),
...
}
// viewport
const fontSize = {
12: pxToVmin(12),
14: pxToVmin(14),
16: pxToVmin(16),
...
}, spacing = {
0: 0,
4: pxToVmin(4),
8: pxToVmin(8),
12: pxToVmin(12),
...
}
module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
// ...
fontSize,
spacing
},
plugins: [],
}

其它

  • 在已有项目中使用,建议使用自定义前缀(如 tw-),方便辨认 tw 类名
  • 安全列表 safelist:作用就是在安全列表的类名不会被意外删除(为提高性能和减少体积,打包时会抖掉无用的类名)
  • 默认的尺寸单位是:px?rem?那么在小程序,如何转为 rpx?
  • 提取组件时,该如何统一命名?

Tailwindcss 的正确打开姿势

参考文章:https://mp.weixin.qq.com/s/55oODhg_3c99J5OdHDP4yg

最显眼的那个痛点可能并不存在

Tailwind 某些元素 class 名可能会非常长,但这个痛点其实有很多方法可以解决(或者说避免复杂的样式)

示例:

<div className='flex items-center text-gray-700 bg-white px-8 py-5 transition hover:bg-amber-100'>
</div>
<div className='flex items-center text-gray-700 bg-white px-8 py-5 transition hover:bg-amber-100'>
</div>
<div className='flex items-center text-gray-700 bg-white px-8 py-5 transition hover:bg-amber-100'>
</div>

以上一大串类名可以在函数组件中,将这些 class 名提取到一个字符串变量中:

var clx = 'flex items-center text-gray-700 bg-white px-8 py-5 transition hover:bg-amber-100'
<div className={clx}></div>
<div className={clx}></div>
<div className={clx}></div>

有的时候,我们使用 tailwindcss 的目的其实是为了少创建一个 css 文件,因此,就近声明变量是我认为最好的方式,只有一些全局的、共用的可以单独提炼出来放到一个单独的文件中去。

无 CSS 是准确方向

Tailwind 支持一种 @apply 语法:将许多 css 样式聚合成一个 class 名,但是这种方式和直接使用 css 就没什么区别了。因此意义并不是特别大。无 CSS 是准确方向!

.btn {
@apply rounded-md border border-solid border-transparent py-2 px-4 text-sm font-medium bg-gray-100
cursor-pointer transition
}

那么该如何处理?

封装思维的小转变,带来极致使用体验

传统的传参思维:

<Button type="primary" size="lg">he</Button>

从 unocss 的使用方式上,获得了一个更简洁的传参思路 —— 那就是把所有的参数类型都设计成布尔型:

<Button danger>Danger</Button>
<Button primary sm>Primary SM</Button>

内部封装,主要是根据不同的参数拼接 className 的字符串,完整实现如下:

export default function Button(props) {
const {className, primary, danger, sm, lg, success, ...other} = props
const base = 'rounded-xl border border-transparent font-medium cursor-pointer transition'

// type
const normal = 'bg-gray-100 hover:bg-gray-200'
const _p = primary ? 'bg-blue-500 text-white hover:bg-blue-600' : ''
const _d = danger ? 'bg-red-500 text-white hover:bg-red-600' : ''
const _s = success ? 'bg-green-500 text-white hover:bg-green-600' : ''

// size
const md = 'text-sm py-2 px-4'
const _sm = sm ? 'text-xs py-1.5 px-3' : ''
const _lg = lg ? 'text-lg py-2 px-6' : ''

const cls = classnames(base, normal, md, _p, _d, _s, _sm, _lg)

return (
<button className={cls} {...other}>{props.children}</button>
)
}

封装好之后,直接使用,可以感受一下极简的传参:

大爱这种使用方式。并且未来组件封装也准备都往这个方向发展~

<Button>Normal</Button>
<Button danger>Danger</Button>
<Button primary>Primary</Button>
<Button success>Success</Button>

便利小工具:cva、twMerge、clsx

clsx 是一个打包体积比 classnames 更小的替代工具。他的功能与 classnames 类似,我们可以用它来组合字符串。

npm i clsx

我们可以通过 clsx 合并字符串,但是这里我们需要注意一个非常容易被忽视的细节 —— 那就是 css 样式优先级的问题:

.red {
background-color: #f44336;
}
.orange {
background-color: orange;
}
<div className='w-80 h-32 red orange mx-auto'></div>
<div className='w-80 h-32 orange red mx-auto'></div>

不管我们如何调整这两个名字的位置,最终的结果都是,显示为 orange —— 这是因为 className 的书写顺序并不能决定元素样式的优先级,它们的优先级跟 css 的声明顺序有关系,如果我们交换他们的位置,你就会发现上面两个元素又都变成了红色

.orange {
background-color: orange;
}
.red {
background-color: #f44336;
}

这个现象的存在,对 tailwindcss 的封装影响非常大。因为很多时候,我们会约定默认样式,然后通过传入新的参数去覆盖默认样式。但是我们传入的只是 className,因此是否能覆盖样式我们无法控制。因此,tailwindcss 专门提供了一个方法来处理这个问题,这个方法就是 twMerge

import {twMerge} from 'tailwind-merge'

twMerge 会根据 className 字符串中的类型合理的删掉被覆盖的样式。例如下面的代码中,px-2 py-1 属于 padding 值,他就会被后传入的同类型 p-3 给覆盖掉。所以最终执行结果只保留 p-3:

twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]');

// 返回结果:'hover:bg-dark-red p-3 bg-[#B91C1C]'

因此,上面的那个 Button 组件封装的案例,我们可以结合 clsx 和 twMerge,修改如下:

export default function Button(props) {
const {className, primary, danger, sm, lg, success, ...other} = props
const base = 'rounded-xl border border-transparent font-medium cursor-pointer transition'

// type
const normal = 'bg-gray-100 hover:bg-gra

// size
const md = 'text-sm py-2 px-4'

const cls = twMerge(clsx(base, normal, md, {
// type
['bg-blue-500 text-white hover:bg-blue-600']: primary,
['bg-red-500 text-white hover:bg-red-600']: danger,
['bg-green-500 text-white hover:bg-green-600']: success,

// size
['text-xs py-1.5 px-3']: sm,
['text-lg py-2 px-6']: lg,
}))

return (
<button className={cls} {...other}>{props.children}</button>
)
}

先用 classnames/clsx 拼接字符串逻辑,然后再用 twMerge 清理掉冗余的 classNames,最后得到的字符串就是最理想的结果

但是并不是所有的 props 都能处理成布尔值传入,或者有的时候你也并不喜欢这种方式,还是更喜欢使用传统的 key=value 的方式传参,那么这个时候,我们可以借助 cva 来实现目标

import {cva} from 'class-variance-authority'

cva 可以帮助我们轻松处理一个属性对应多个值,每个值又对应多个 className 的情况。他的具体使用方式如下:

const cvacss = cva(
'rounded-md border border-transparent font-medium cursor-pointer transition', {
variants: {
type: {
normal: 'bg-gray-100 hover:bg-gray-200',
primary: 'bg-blue-500 text-white hover:bg-blue-600',
danger: 'bg-red-500 text-white hover:bg-red-600'
},
danger: {
true: 'bg-red-500 text-white hover:bg-red-600',
false: 'bg-red-500 text-white hover:bg-red-600'
}
},
defaultVariants: {
type: 'normal',
danger: false
}
}
)

通过传入参数 type、size,结合 twMerge 可以得到最终值:

const cls = twMerge(cvacss({ type, size }))

我们可以组合这两种思维一起使用,能处理成布尔值传入的参数就处理成布尔值,不能处理的就使用这种方案,结合起来之后的组件封装使用体验会高很多!

额外配置插件,让智能提示更智能

接下来就是重头戏了。这个配置对于使用体验的提升至关重要。我们都知道,使用一个插件 IntelliSense 可以在 html 中编写 css 的时候,会自动提示相关的 tailwindcss 属性值。因为值太多了记不住,所以这个插件是我使用 tailwindcss 的必要条件

但是接下来问题就来了,因为我为了简化 className 的长度,经常需要把一些 class name 抽象到别的地方去,但是其他地方写 tailwindcss 的时候就不支持智能提示了,这个就很蛋疼

好在我们可以通过配置正则的方式,识别到其他的使用场景,从而让特定的场景中也支持这种智能提示。

我的看法:看到最后,还是放弃这种方案。作者这种更适合用来写复杂的组件,平时写业务代码的时候,还是不要用这种方案了,怎么开发方便就怎么来。

Footnotes

  1. https://tailwindcss.com/

  2. https://npmtrends.com/tailwindcss

  3. https://learnku.com/laravel/t/53827

  4. Tailwind CSS IntelliSense https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss

  5. https://www.npmjs.com/package/purgecss