TailwindCSS 基本介绍与最佳实践

2,933 阅读10分钟

基本介绍

84655F52-052F-489F-910D-966895F9542E.png


一个功能类优先的 CSS 框架,用于快速构建定制的用户界面。这是来自 TailwindCss 官方定义。

中文网站

本文先围绕 功能类优先(utility-first) 讲一下 TailwindCss 核心特点,再讲一下几个配置项以及配置时的一些小 tips,最后再一起探讨下有关 TailwindCss最佳实践

活跃度

github starts 数量达到 45.3k

npm 月下载量达到了 3,673,301(2021.06.06-2021.07.05),同期 vue 的下载量为 10,848,286

成绩还是不错,我们可能会想起迅哥儿说过的一句话,世界上本没有路,走的人多了也便成了路。

功能类优先(utility-first)

在一组受约束的原始功能类的基础上构建复杂的组件。

<div class="bg-white text-primary mx-auto"></div>

1. 原子化 CSS (Atomic CSS)

CSS 原子化是指定义一组表示单一用途样式单元的类。

另外还有 CSS 组件化,了解两者可参考文章 「CSS 思维」组件化 VS 原子化

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

2. 较好的语义化

使用 TailwindCss 你不用花精力来定义类名,你可以使用内置具有良好语义化的类名,实现样式效果。你也可以一定程度定义符合你自己规则的类名,例如加上统一的前缀。

TailwindCss 语义化也并不完美,默认的命名方案有一定的记忆成本。在后面最佳实践,会具体谈谈这个问题,提供一些其他解决方案。

3. 约束性

使用 TailwindCss 功能类,是从预定义的设计系统中选择样式,这使得构建统一的 UI 变得更加容易。这也是 CSS 原子化与直接使用内联样式有着明显差异,TailwindCss 具有约束性。

4. 响应式

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

5. Hover, focus, 以及其它状态

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

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

断点前缀最小宽度CSS
sm640px@media (min-width: 640px) { ... }
md768px@media (min-width: 768px) { ... }
lg1024px@media (min-width: 1024px) { ... }
xl1280px@media (min-width: 1280px) { ... }
2xl1536px@media (min-width: 1536px) { ... }
<img class="w-16 md:w-32 lg:w-48" src="...">

也可以在 tailwindcss.config.js 文件中完全自定义您的断点。

配置

1. 初始化配置

通过命令 npx tailwindcss init 就可以生成一个初始配置文件 tailwindcss.config.js,默认配置如下:

module.exports = {
    purge: [],
    darkMode: false, // or 'media' or 'class'
    theme: {
        extend: {},
    },
    variants: {
        extend: {},
    },
    plugins: [],
}

你只需指定要更改的内容即可,缺少的部分将会使用 TailwindCss默认配置

使用默认配置,TailwindCSS 的开发版本(2.2.4)是 3566.2 KB 未压缩, 用 Gzip 进行压缩可到 289.2 KB,用 Brotli 进行压缩 71.3 KB。

UncompressedMinifiedGzipBrotli
3566.2 KB2872.2 KB289.2 KB71.3 KB

2.具体配置(tips)

官网中文网 有比较详细的配置说明,下面介绍一些,其中也包括一些使用 tips。

1.PostCss

我们通常会采用以 PostCSS 插件的形式安装 TailwindCSS,项目根路经下创建 postcss.config.js,配置如下:

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

2.Preflight

一套武断的针对 TailwindCss 项目预设的基础样式。

Preflight 是一套针对 TailwindCss 项目的基础样式,旨在消除跨浏览器的不一致性,并使您的工作更轻松地符合设计系统的约束。

ol,
ul {
    list-style: none;
    margin: 0;
    padding: 0;
}
...

有关 Preflight 采用的所有样式的完整参考,请参见 样式表

你也可以在 tailwind.config.js 配置禁用:

module.exports = {
    corePlugins: {
        preflight: false,
    }
}

3. 生产优化 Tree-Shake

当构建生产时,应总使用 TailwindCss的 purge 选项来 tree-shake 优化未使用的样式,并优化最终的构建大小。

module.exports = {
    purge: {
        enabled: process.env.NODE_ENV === 'production',
        content: ['./src/**/*.vue'],
    },
    ...

}

4. 预设 Presets

默认情况下,预设本身会扩展 TailwindCss默认配置

团队间开发应当保持统一性,可以定义一份 tailwind.config.preset.js 作为团队的预设配置。由于业务差异性,或者一些历史因素,导致某些项不得不做一些更改,那么在保证使用团队内部共用的配置前提下,可以利用 Presets,将 tailwind.config.preset.js 作为预设。

module.exports = {
    presets: [
        require('@df/df-universal-suite/src/tailwind.config.preset.js')
    ],
    screens: {
        mobile: '640px',
        pc: '1280px',
    },
    ...
}
5. 变体 Variants

TailwindCss 通过 变体前缀原子功能类 组合形成新的变体功能类,实现了 响应式变体('responsive')、深色模式变体('dark') 和 悬停、焦点和其他状态变体('hover', 'focus', ...)。

module.exports = {
    darkMode: false, // or 'media' or 'class'
    theme: {
        colors: {
            white: {
                DEFAULT: '#fff',
            },
        },
        screens: {
            mobile: '640px',
            pc: '1280px',
        },
    },
    variants: {
        backgroundColor: ['responsive', 'dark', 'hover'],
    },
    ...
}

生成 css:

.bg-white, .hover\:bg-white:hover {
    --tw-bg-opacity: 1 !important;
    background-color: rgba(255, 255, 255, var(--tw-bg-opacity)) !important
}
.mobile\:bg-white, .mobile\:hover\:bg-white:hover {
    --tw-bg-opacity: 1 !important;
    background-color: rgba(255, 255, 255, var(--tw-bg-opacity)) !important
}
.pc\:bg-white, .pc\:hover\:bg-white:hover {
    --tw-bg-opacity: 1 !important;
    background-color: rgba(255, 255, 255, var(--tw-bg-opacity)) !important
}

这里生成了 6 个 原子功能类。

设问:

当启动深色模式即当 darkMode 值为 'media' 或 'class'时, 会生成多少个原子功能类呢?

答:

启动深色模式,会再衍生 dark 变体(如 .dark:/bg-white, .dark:hover\:bg-white:hover,... )功能类,所以是 12 个。

6. colors

colors 可以为你的项目定制默认调色盘, 默认情况下,注意这些颜色会被所有颜色驱动的功能类自动共享,如 textColorbackgroundColorborderColor

module.exports = {
    theme: {
        colors: {
            primary: {
                DEFAULT: 'var(--primary-color)',
                100: 'var(--primary-color-100)'
            },
        },
    },
    ...
}

此时生成原子功能类 text-primary, text-primary-100, bg-primary, bg-primary-100,border-primary, border-primary-100 及其各自的 变体原子功能类

7. spacing

可以通过 theme.spacing 来定制大小, 注意间距值将自动由 paddingmarginwidthheightmaxHeightgapinsetspacetranslate 核心插件继承

module.exports = {
    theme: {
        spacing: {
            4: '4px',
            8: '8px',
        },
        ...
    },
    ...
}

配置将自动生成 mb-4pb-4 等原子功能类。

8. extend

tailwind.config.js 文件中,themevariants 是可以支持 extend 属性的,可以将配置继承过来,以一个例子来说明。

// 配置 A
module.exports = {
    theme: {
        extend: {
            textColor: {
                white: '#ffffff',
            }
        },
        colors: {
            primary: {
                DEFAULT: 'var(--primary-color)',
                100: 'var(--primary-color-100)',
            },
        },
    },
    ...
}

// 配置 B
module.exports = {
    theme: {
        colors: {
            primary: {
                DEFAULT: 'var(--primary-color)',
                100: 'var(--primary-color-100)'
            },
        },
        textColor: {
            white: '#ffffff',
        }
    },
    ...
}

配置 A 和 配置 B 的区别是,配置 A 使用了 extend,对 textColor 进行补充,而配置 B 则是对 textColor 进行了覆盖。

导致结果为:

A 中字体颜色拥有 text-white、text-primary、text-primary-100

B 中字体颜色仅有 text-white

提问:配置 C 中字体颜色有几个值?

// 配置 C
module.exports = {
    theme: {
        extend: {
            colors: {
                primary: {
                    DEFAULT: 'var(--primary-color)',
                    100: 'var(--primary-color-100)',
                },
            },
            textColor: {
                white: '#123456',
            }
        },
    },
    ...
}

上面讲 预设 Presets 时提到,在没自定义预设的情况,TailwindCss 会将默认配置作为预设,因为这里没有对 colors 进行覆盖,而是在 extend 了默认配置的 colors,所以这里颜色值会包含默认配置的颜色值。

9. resolveConfig

TailwindCss 提供了一个 resolveConfig 函数,我们可以拿到最终生成的配置的对象。我们可以通过 js,拿到我们指定配置的值。

import resolveConfig from 'tailwindcss/resolveConfig';
import tailwindConfig from '../tailwind.config.js';
const fullConfig = resolveConfig(tailwindConfig);
const theme = fullConfig.theme;
...

最佳实践

关于 TailwindCss 的最佳实践,不同业务场景,关注的东西不太一样,大家可以一起探讨、补充,不对的敬请指正。

1. 不要使用字符串连接来创建类名

在生产优化时,purgeCss 是通过正则匹配 html 类的属性,并不会解析 html,purgeCss 将不知道保存这些通过字符串连接的类。

那么在项目中,一定不要字符串连接类名。

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

2.尝试选择数字标签而不是语义标签

之前谈到 TailwindCss 具有较好的语义化,但是 TailwindCss 默认的命名方案,增加了不少开发者的记忆成本,例如最新版本(2.2.7)对 字体粗细 值从 thin(100) 定义到了 black(900)。

将精确值和其名称分离,这样命名的好处是可以更容易替换每个值,例如当设计希望我们将 font-normal 从 400 提升至 450 时,我们可以通过修改 normal 的值为 450,而如果使用数字的方式,我们需要把 font-400 的值改为 450,并对齐类名,修改每个用到的地方为 font-450,不过我们也能通过全局查找替换的方式来解决。

而数字标签的方式可以减少UI工具的值转换为 TailwindCss 类的成本。例如,font-weight: 600; 究竟是对应 font-bold 还是 font-semibold,而 font-600 很明确了。

当然并不是语义标签总是不好的,并不是都是使用数字标签,需要确定哪种方式让我们使用起来更加受益。

fontWeight: {
    thin: '100',
    extralight: '200',
    light: '300',
    normal: '400',
    medium: '500',
    semibold: '600',
    bold: '700',
    extrabold: '800',
    black: '900',
}

fontWeight: {
    100: '100',
    200: '200',
    300: '300',
    400: '400',
    500: '500',
    600: '600',
    700: '700',
    800: '800',
    900: '900',
}

3.更加易读的配置信息

可以定义一些公共函数,让配置变得更加易读。

下面有这样的一段配置:

screens: {
    sm: '40em',
    md: '48em',
    lg: '64em',
    xl: '80em',
},
borderWidth: {
    2: '2px',
    3: '3px',
    5: '5px',
},
fontSize: {
    12: '0.75rem',
    13: '0.8125rem',
}

在实际开发中,UI 设计师、或者 UI 工具为我们提供的更多的是 px 单位的数值类型,而 40em0.75rem 显得不那么易读。另外一个 2: '2px' 这样的配置又使得我们失去了对 rem 的依赖,我们可以尝试通过定义函数,将上面的配置替换为下面的配置:

const em = px => `${px / 16}em`;
const rem = px => ({ [px]: `${px / 16}rem` });
const px = num => ({ [num]: `${num}px` });
screens: {
    sm: em(640),
    md: em(768),
    lg: em(1024),
    xl: em(1280),
},
borderWidth: {
    ...px(2),
    ...px(3),
    ...px(5),
},
fontSize: {
    ...rem(12),
    ...rem(13),
}

4. 不建议使用 @apply

Adam Wathan (Tailwind’s creator) 曾公开表示过,@apply 只是那些因为类名列表过长的人来使用这个框架取巧的一种方式。

从 @apply 的功能上来说,这会生成新的功能类,产生多余的 css,我们应尽量不使用它,这与 TailwindCss 设计背道而驰。

5. TailwindCss 智能感知

推荐使用官方为 Visual Studio Code 用户准备的 TailwindCSS 智能感知工具

Webstorm 只需要启用插件,可以参考文章

6. Translate css to tailwind

我们在项目之初就应用 TailwindCss,会有比较丝滑的体验。而对于老项目迁移到 TailwindCss,这可能是一个比较难受的过程,这里可以介绍一个在线工具,可以根据你的 tailwind.config.js 文件,将 CSS 转化为 TailwindCss 类名。

.logo {
    margin-bottom: 1.6rem;
    background: url('logo.svg') no-repeat;
    display: flex;
    justify-content: center;
}

.footer {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-direction: row-reverse;
    padding: 2.4rem 3rem;
    border-top: 1px solid #fff5f5;
}

转化后:

/* ℹ️ Base selector: .logo */
/* ✨ TailwindCSS: "bg-no-repeat flex justify-center mb-6" */
/* ⚠️ Some properties could not be matched with Tailwind classes. Use @apply to extend a CSS rule: */

.logo {
@apply bg-no-repeat flex justify-center mb-6;
background-image: url(logo.svg);
}

/* ℹ️ Base selector: .footer */
/* ✨ TailwindCSS: "border-red-100 border-solid border-t flex flex-row-reverse items-center justify-between py-10 px-12 w-full" */

这个工具是基于 TailwindCss 1 的版本,具体支持情况需要自己尝试。

相关资源:

参考资料: