第 2 章 Tailwind 基础
Tailwind 看起来像是个反直觉的方案,用来解决复杂网站中 CSS 管理的问题。 它由大量的小型实用类(utility class)组成,每个类通常只设置一个特定的 CSS 属性,并赋予它一个特定的值。 在 Tailwind 中,实现复杂样式的首选方式,是在 HTML 元素上组合使用多个 CSS 类。
这种模式与多年来发展出来的许多 CSS 命名规范背道而驰。 许多 CSS 框架和命名规范都建议使用能反映页面元素语义意义的名字——比如 button、nav-bar 或 menu-item。
而 Tailwind 的类名完全不具备语义。它们是“工具类(utility classes)”,也就是说,每个 Tailwind 类都代表一个具体的 CSS 属性,比如 font-bold 表示文字加粗,m-6 表示外边距。
其他 CSS 框架虽然也包含工具类,但通常仍然把语义化的类名放在更重要的位置。 使用 Tailwind 以及这些工具类,意味着代码中可能会出现大量重复,因为同样的 Tailwind 工具类经常会在多个 DOM 元素上被反复使用。
尽管可能会有重复,Tailwind 依然能很好地应用在大型网站上。 其中一个原因是,当你在某个地方应用一个 Tailwind 类时,这个样式改动的性质和作用范围都非常清晰。
Tailwind 的类名一开始可能看起来有点晦涩,但它的命名模式非常一致,用久了就会变得容易理解。 另外,Tailwind 的修饰符(modifier)让你能在 HTML 中轻松定义特殊的行为,比如 hover 效果,或者在不同屏幕尺寸下的响应式样式。 这些修饰符也让你只看 HTML,就能更清楚地了解一个元素的完整样式。
因为 Tailwind 的类可以随意组合,所以相比其他 CSS 写法,你几乎不用写多少外部 CSS。 使用 Tailwind 时,也不需要再命名那么多自定义类名。 而且因为样式改动与 HTML 结构紧密关联,你对修改结果的预期也更容易、更直观。
在使用 Tailwind 时,你也可以从一堆工具类中提取出一些通用样式,做成一个更语义化的类名。 不过 Tailwind 并不推荐你用这种方式去创建自定义类。 相反,它建议你利用前端技术栈中已有的工具来减少重复。
比如,与其单独写一个按钮样式的 CSS 类,不如创建一个可复用的 React 组件,或者在 Rails 中用一个 partial 或 helper 方法,并只在这个可复用组件里定义一次相关的样式。
Tailwind 本身由几个部分组成:我们在本书中主要会用到的工具类(utility classes)、一个用于样式重置的样式表(reset stylesheet),以及一些让使用 Tailwind 更方便的辅助函数。
工具类(Utilities)
Tailwind 的工具类是你最需要理解的核心部分。下面是它们的工作原理,以及本书中将如何讲解它们。
Tailwind 由成千上万,甚至可能上百万个工具类组成,这些类大多数只用于设置单个 CSS 属性的值。 例如,Tailwind 的 font-bold 工具类其实就是 CSS 属性 font-weight: 700 的一种简写。 你可以在 HTML 元素的 class 属性中这样使用它:class="font-bold"。
Tailwind 的潜在工具类数量非常庞大——远远超过你在任何一个项目中会用到的数量,也不可能全部加载到浏览器里。 为了控制生成的 CSS 体积,Tailwind 提供了一个命令行工具,它会根据你的代码自动生成实际使用到的那部分工具类。
此外,Tailwind 的配置文件还能让你更精确地控制它在代码中搜索的类名模式。 除非我特别说明,在本书中,我讨论的都是基于最小化配置所生成的默认类集。 而在第 8 章“自定义 Tailwind”(第 67 页)中,我会介绍如何调整 Tailwind 搜索的类名模式。
Tailwind 的工具类通常成组出现,遵循相似的命名前缀或后缀规律。 当我在书中提到这些类族时,会用类似 .text-{size} 这样的写法,表示一整个类的系列,比如 .text-xs、.text-sm、.text-xl 等。 当使用这种语法时,只有当花括号内有内容时才需要加上中划线,所以会有 .text-sm,但也可能只有 .text。
工具类名中的可变部分(variable part)不一定要出现在结尾。 例如,在设置外边距(margin)的工具类中,.m{direction}-{size} 表示一组类,比如 .m-0 或 .mt-10。 你会发现,Tailwind 中这些变量部分在不同的工具类之间往往是保持一致的。 比如,margin 工具类中的 {size} 和 {direction} 选项,同样也适用于 padding 工具类和其他几个相关类族。
虽然 Tailwind 为尺寸、颜色等属性提供了一套默认值,但你也可以用方括号包裹任意值来实现自定义。 例如,如果你想设置一个特殊的外边距,可以写成 m-[104px],表示 104 像素的外边距(这个值并不在默认尺寸中)。 通常来说,只要类名里存在变量占位符的位置,你都可以用方括号语法来插入任意值。
这种“任意值”主要是为了临时的特殊样式而设计的。 如果你发现自己经常重复使用某个自定义值,那么最好把它加进 Tailwind 的配置文件中,让它成为全局可用的选项,同时保持设计的一致性。
你甚至可以用方括号插入整个 CSS 属性,比如 [mask-type:alpha],在需要使用 Tailwind 默认未覆盖的属性时就很有用。
预设样式(Preflight)
当你安装 Tailwind 时,需要通过以下三条命令导入三个不同的文件: @tailwind base、@tailwind components 和 @tailwind utilities。 每个文件都包含一组不同的 CSS 规则。(在某些安装方式中,你可能会用更通用的 @import 命令来代替 @tailwind。)
其中,@tailwind base 包含了 Tailwind 的重置样式表——Preflight。 所谓“重置样式表”,就是对所有基础 HTML 元素重新定义样式,只保留最简化的一组基础样式属性。
如果没有这样的重置样式表,每个浏览器都会按照自己的默认规则去渲染 HTML 元素,而这些默认样式往往彼此不同。 使用 Preflight 这样的重置样式,可以让我们的应用从一个统一、干净的起点开始,消除不同浏览器之间的差异,同时为我们后续添加的自定义样式提供一个更简洁的基础。
你可以通过查看文件 node_modules/tailwindcss/dist/base.css 来看到 Tailwind 使用的完整重置样式。 总的来说,Preflight 做了以下几件事:
- 覆盖所有标题的默认样式,例如
h1的显示效果与基础文本相同。 - 移除
ul和ol列表的默认样式,所以默认没有圆点,这在列出要点时有点讽刺。 - 将通常带有外边距的元素的所有外边距设置为 0。
- 将所有边框默认设置为 0 像素宽、实线,并使用定义好的边框颜色。
- 给按钮一个默认边框。
- 将图片和类似图片的对象设置为
display: block,而不是display: inline,意味着它们会像段落一样独占一行(就像div标签),而不是像行内元素一样与文本同行(就像span标签)。
如果你只使用 Preflight 样式,页面看起来会很平淡,但这正是它的目的。 使用 Preflight 可以确保任何显示样式的更改都是由我们明确、主动地添加的。
@tailwind components 文件比较小,它只包含 container CSS 类的定义,这些类通常用于页面的顶层,用来定义整个页面的布局容器。 我会在第 5 章“页面布局”(第 41 页)中详细讲解这一部分。
而大部分被认为是 Tailwind 核心内容的,都在 @tailwind utilities 文件中,它定义了所有工具类及其各种变体。 本书的大部分内容都会围绕这个文件的内容展开讲解。
重复问题(Duplication)
在使用 Tailwind 时,一个常见的顾虑是如何管理重复,尤其是当你需要写很长的类列表来实现设计目标时。 也就是说,如果每次都要为 h1 写上 class="text-6xl font-bold text-blue-700",就像我们在导言(第 xiii 页)里做的那样,那么每次都保持一致,不正是要输入很多重复内容吗? 更何况,如果你的 h1 设计发生了变化,该怎么办呢?
在代码中管理重复
Tailwind 确实提供了一些方法来管理 CSS 类列表的重复,但你也应该把重复问题放在更大的代码结构中来看,而不仅仅当作一个 CSS 问题。 无论你使用什么工具来构建 HTML 标记,它很可能已经有组件或函数机制,用于减少代码重复。使用 Tailwind 时,将 CSS 类列表视作整体代码的一部分,是个好习惯。
例如,如果你在使用 React,你就有组件。许多其他前端框架同样提供组件机制。 与其在 CSS 中管理重复,不如创建包含常用 Tailwind 类的 React 组件:
export const Header = ({children}) => {
return (
<div className="text-6xl font-bold text-blue-700">
{children}
</div>
)
}
export const SubHeader = ({children}) => {
return (
<div className="text-4xl font-semibold">
{children}
</div>
)
}
export const SubSubHeader = ({children}) => {
return (
<div className="text-lg font-medium italic">
{children}
</div>
)
}然后你就可以这样使用它:
<Header>Cool Text</Header>
<SubHeader>Less Cool Text</SubHeader>
<SubSubHeader>Kind of boring text</SubSubHeader>在普通的 JavaScript 中,你也可以创建一个函数,返回一串 Tailwind 类名:
const title = () => { return "text-6xl font-bold text-blue-700" }在 React 中,你可以这样使用:
<Component className={title}>Cool Text</Component>在 Ruby on Rails 中,你同样可以为 Tailwind 类列表定义辅助方法(helper method):
def title
"text-6xl font-bold text-blue-700"
end或者,你可以创建一个 ERB partial,比如命名为 app/views/partials/_title.erb:
<div className="text-6xl font-bold text-blue-700">
<%= yield %>
</div>这里的 yield 很重要,因为它允许你在调用 partial 时传入包含子内容的代码块。语法看起来有点奇怪:
<%= render partial: "partials/_title" do %>
<h2>Whatever</h2>
<%= end %>代码块内部可以包含任意 ERB 代码,这些内容会被插入到 yield 所在的位置。
如果你不喜欢这些语法方式,更倾向于用 CSS 解决重复问题,Tailwind 提供了一个 CSS 指令 @apply,以及一个叫 @layer 的指令,我们接下来会讲到这两个。
使用 @apply 处理重复
@apply 指令允许你在其他 CSS 选择器的定义中使用 Tailwind 类。 比如,我们可以用它在 CSS 中重新定义标题样式:
@layer components {
.title { @apply text-6xl font-bold }
.subtitle { @apply text-4xl font-semibold }
.subsubtitle { @apply text-lg font-medium italic }
}然后你就可以像使用普通 CSS 类一样使用它们:
<div class="title">Title</div>@layer 指令可以是 base、components 或 utilities。 对于浏览器来说,只要你使用了 @layer,这些选择器就会被视作属于你声明的那一层,无论它们实际在 CSS 文件中的位置在哪里。
使用 @layer components 会将选择器定义为组件层的一部分,并且位于工具类(utilities)之前。 这意味着,如果你把自定义样式和 Tailwind 工具类一起使用,Tailwind 工具类会覆盖你的自定义样式,这正是我们希望的效果。
比如,我们可以这样定义一个超大标题(extra big title):
<div class="title text-5xl">Title</div>要理解 @layer 为什么重要,你需要了解一个 CSS 的基本原则:在其他条件相同的情况下,如果两个 CSS 类都试图设置同一个属性,后定义的那个会生效。 (如果你熟悉 CSS,还知道有一个“特异性原则”,即更具体的选择器优先,但由于 Tailwind 所有工具类的特异性相同,这里不成问题。)
在 CSS 文件中,如果同一个选择器有两条定义设置了相同的属性,文件中后出现的那条会生效。 在 Tailwind 中,如果两个工具类设置了相同的属性,列表中后面的类会生效。 例如,class="text-xl text-2xl" 会让文本显示为 2xl 尺寸。
通过在 @layer 中定义自定义选择器,该选择器会在该层的末尾加载,并位于下一层之前。 这会影响自定义 CSS 与其他 Tailwind 工具类或 CSS 的交互方式。
例如,我们可以通过在标签上使用 @apply(而不是类选择器)来把定义直接应用到 HTML 中。 在这种情况下,我们将定义放在 base 层:
@layer base {
h1 { @apply text-4xl font-bold }
h2 { @apply text-2xl font-semibold }
h3 { @apply text-lg font-medium italic }
}在这里,我们直接重新定义了 h1、h2 和 h3 元素,所以就可以这样使用:
<h1>Title</h1>因为这些定义在 base 层,所以会在所有工具类之前生效,这样 <h1 class="text-6xl"> 就能按预期显示,6xl 的样式会优先生效。 如果 h1 被定义在 utilities 层,那么 h1 会优先,因为它定义在 text-6xl 之后。
而且,由于我们把层级放在了 base,Tailwind 会把它视作 Preflight 样式的一部分,并在任何组件之前定义。 这种布局方式让你可以按预期自由组合标签、组件和工具类。
这一切都非常实用,让你能够用 Tailwind 工具类作为构建块,逐步建立起自己的框架。 但同时也要意识到,这意味着你实际上在构建一个框架,同时需要承担相应的命名和维护责任。
修饰符(Modifiers)
在介绍工具类之前,还有一个 Tailwind 特性需要讲:修饰符。
修饰符是 Tailwind 在 HTML 标记中使用 CSS 伪类、伪元素和媒体查询的方式。 例如,我们经常希望当用户把鼠标悬停在某个元素上时,它显示不同的效果,这对应 CSS 中的伪类 hover。
在 Tailwind 中,你可以通过给其他工具类添加修饰符来基于 CSS 伪类定义工具类。 比如,如果你希望锚点(a 标签)在鼠标悬停时显示下划线,可以这样写:
<a class="hover:underline">Click me</a>这种写法简洁、易读,而且与 HTML 一起定义,所以清楚地知道什么时候生效。 你可以在任何 Tailwind 工具类前使用 hover: 修饰符。 甚至可以在任意 CSS 样式前使用,比如 hover:[mask-type:luminance]。 修饰符也可以组合使用,例如:hover:dark:underline。
正如你将在第 7 章“响应式设计”(第 59 页)看到的,Tailwind 还使用修饰符根据屏幕宽度调用不同的工具类。 例如,你可以写 class="sm:m-2 lg:m-4",元素会随着屏幕变宽而增大外边距。
Tailwind 定义了 20 多种修饰符,并且在你使用它们时,Tailwind CLI 会自动生成相应的 CSS。
你甚至可以把修饰符和 @apply 结合使用,例如 @apply hover:underline 就是合法的,用来定义新的 CSS 类。
CSS 单位(CSS Units)
CSS 中大多数用于定义长度或宽度的值都可以带上单位。 高度和宽度还可以使用百分比。
CSS 定义了两类单位:绝对单位和相对单位。
绝对单位是以真实世界的单位来定义的,比如你可以把宽度设置为 5in(英寸)。 更常见的是使用 px(像素)。很久以前,一个 px 代表一个实际的显示像素,但现在电脑和手机屏幕的像素密度更高了,CSS 中的像素被定义为 1/96 英寸(也就是把一英寸分成 96 份)。 字体大小通常会用 pt(磅),比如 font-size: 20pt。 1 磅等于 1/72 英寸,这种测量方法早在计算机出现之前就已经存在。
在 CSS 中,你更常见的是相对单位,其中最常用的是 em,表示元素自身的大小,例如 width: 10em。 用相对单位定义字体大小很常见,但因为 font-size: 1.5em 会形成循环定义,对于排版属性来说,em 指的是父元素的字体大小,而不是当前元素自身的字体大小。
如果这有点让人困惑——确实如此——它也不够稳定,因为修改字体大小可能会对所有使用 em 定义的样式产生意想不到的连锁影响。 更稳定的替代方案是 rem,它指的是根元素的字体大小,在 Tailwind 的重置样式系统中默认是 16px。 在 Tailwind 中,大多数长度单位要么用百分比,要么用 rem 来定义。
接下来,我们看看 Tailwind 提供的排版样式工具。