如果您一直关注最新的 CSS 框架,您可能已经听说过最新的框架:Tailwind CSS。根据其文档,“Tailwind 是一个实用优先的 CSS 框架,用于快速构建自定义用户界面。”
实际上,这意味着使用各种与底层 CSS 属性密切相关的类。例如,将类似 .text-center
的类应用于元素意味着我们将它的 text-align
属性设置为 center
。很简单,对吧?
使用这样的实用类,我们可以花更多时间迭代设计,避免过早抽象 CSS。然后,一旦我们对设计感到满意,Tailwind 可以轻松地将我们的实用程序提取到组件类中。
现在,我确信您知道,即使提到一个实用程序框架,也会邀请一定程度的喧嚣。在我们开始在 Twitter 或评论中对我大喊大叫,仅仅因为我提到了一个实用程序框架之前,让我们花点时间记住,Tailwind 只是我们可用的一个可能工具。
如果您不想将其添加到您的工具箱中,那么不用担心——您自己决定!但是,如果您有兴趣至少了解这个新工具,让我们一起看看如何构建一个注册表单。
话不多说,让我们使用 Tailwind 设置一个新的项目,编写一些 HTML 代码,并对其进行样式化。
设置
让我们首先创建一个目录,供我们从中工作。使用您的终端,导航到您要创建项目的目录,然后运行 mkdir <your-project-name>
。现在,让我们 cd
到我们的新项目中,并按照 Tailwind 安装指南 进行操作。
由于我们想看到 Tailwind 的所有功能,让我们使用 npm 或 Yarn 使用以下命令安装它。
# Using npm
npm install tailwindcss --save-dev
# Using Yarn
yarn add tailwindcss --dev
安装完 Tailwind 后,我们现在可以通过运行 ./node_modules/.bin/tailwind init
生成配置文件。这会在我们项目的根目录中为我们生成一个 tailwind.js
文件。在这个文件中,我们可以调整 Tailwind 的默认设置,以满足我们项目的需要。对于这个项目,我们不应该做任何更改。
现在,让我们创建一个 CSS 文件,在其中我们可以管理自己的 CSS 并注入 Tailwind 样式。为此,我们可以从项目目录运行 touch styles.css
。
在这个新文件中,我们可以使用 Tailwind 的 @tailwind
指令将 preflight
和 utilities
样式注入到我们的 CSS 中。preflight
包含所有基础样式和一些浏览器样式规范,而 utilities
添加了我们在配置文件中指定的实用程序类。因此,我们的 styles.css
文件应该如下所示
@tailwind preflight;
/* Here we can add any custom overrides */
@tailwind utilities;
/* Here we can add our own utilities or custom CSS */
如果您使用的是 PHPStorm,并且对 @tailwind
在 CSS 文件中的红色波浪线感到厌烦,只需在您使用它的位置上方添加 /*noinspection CssInvalidAtRule*/
。
设置好之后,我们可以运行 ./node_modules/.bin/tailwind build styles.css -o main.css
生成我们想在项目中使用的 CSS 文件。这似乎有点繁琐,但不要担心!Tailwind 与 Webpack、Gulp 或 Laravel Mix 等适当的构建工具一起使用,因此在大型项目中,您可以设置好并忘记它。
这将完成我们的 Tailwind 设置!现在,我们可以开始编写 HTML 代码了。
我们的 HTML
在我们对注册表单进行样式化之前,我们需要构建它!首先,我们需要一个简单的 index.html
文件。因此,从根目录,您可以运行 touch index.html
来创建文件。然后,我们可以添加以下代码片段来开始。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tailwind Intro</title>
<link rel="stylesheet" href="main.css">
</head>
<body>
</body>
</html>
如您所见,它是一个典型的 HTML 页面。这里唯一的区别是我们导入 main.css
文件,并为页面提供了一个描述性的标题。现在,让我们开始构建注册表单!
首先,让我们将两个嵌套的 <div>
元素添加到 <body>
标记的内部。
<body>
<div>
<div>
</div>
</div>
</body>
我们将使用外部 <div>
来定位页面,而内部 <div>
将是表单的包装器。现在,在内部 <div>
中,我们可以添加一个 <h1>
来标记表单,以及一个 <form>
。
<div>
<h1>Sign Up</h1>
<form action="/" method="post">
</form>
</div>
我们现在真的在火力全开!要完成表单,我们只需要添加 <label>
元素、<input>
元素和 <button>
。在添加它们时,让我们将每个 <label>
<input>
对包装在一个 <div>
中,这样它们就会保持在一起。
<form action="/" method="post">
<div>
<label for="first_name">First Name</label>
<input type="text" name="first_name" id="first_name">
</div>
<div>
<label for="last_name">Last Name</label>
<input type="text" name="last_name" id="last_name">
</div>
<div>
<label for="email">Email</label>
<input type="email" name="email" id="email">
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password" id="password">
</div>
<button type="submit">Create Account</button>
</form>
最后,让我们添加一个链接,用于在表单下方访问登录页面。
<div>
<!-- Form is here -->
<a href="/login">Already have an account?</a>
</div>
将所有这些放在一起,我们的 HTML 代码将如下所示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tailwind Intro</title>
<link rel="stylesheet" href="main.css">
</head>
<body>
<div>
<div>
<h1>Sign Up</h1>
<form action="/" method="post">
<div>
<label for="first_name">First Name</label>
<input type="text" name="first_name" id="first_name">
</div>
<div><label for="last_name">Last Name</label>
<input type="text" name="last_name" id="last_name">
</div>
<div>
<label for="email">Email</label>
<input type="email" name="email" id="email">
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password" id="password">
</div>
<button type="submit">Create Account</button>
</form>
<a href="/login">Already have an account?</a>
</div>
</div>
</body>
</html>
非常简单,对吧?现在,当我们在页面上看到它呈现出来时,我们应该看到类似这样的东西

如果看起来 <input>
消失了,不要惊慌;这只是浏览器重置在起作用。最后,我们准备看看这个 Tailwind CSS 是怎么做的了。
使用 Tailwind CSS
作为优秀的开发人员,让我们采用移动优先方法 来为我们的注册表单设置样式。因此,在 400px
的较小视窗宽度下,我们的页面看起来像这样

为我们的表单字段设置样式
让我们开始使用 Tailwind 为 <input>
设置样式。首先,让我们添加一个边框,这样我们就能在页面上看到它。为此,我们只需要添加 .border
类即可。因此,现在我们的第一个名字 <input>
将如下所示
<input class="border" type="text" name="first_name" id="first_name">
现在我们可以在屏幕上看到它了!

这有多容易?让我们继续添加一些填充,并将文本颜色稍微变淡。为此,我们只需要添加以下类:.py-2
、.px-3
和 .text-grey-darkest
。
<input class="border py-2 px-3 text-grey-darkest" type="text" name="first_name" id="first_name">
使用前两个类,我们利用了 Tailwind 附带的填充比例,并将其垂直和水平应用于元素。如果您想定义自己的比例,只需跳到您的配置文件中,将其更改为您需要的比例即可。使用最后一个类,我们使用 Tailwind 的默认颜色调色板,并将元素的颜色更改为最深的灰色。
让我们更进一步。现在,我们可以将 <label>
定位到 <input>
的上方,并为它们添加一些样式。
<div class="flex flex-col mb-4">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="first_name">First Name</label>
<input class="border py-2 px-3 text-grey-darkest" type="text" name="first_name" id="first_name">
</div>

看看,我们的第一个名字字段看起来很棒!最好的部分是我真的不需要解释这些类在做什么——它们自己解释了!但为了确保我们都在同一页,让我快速浏览一下它们。
外部 <div>
的 display
属性通过 .flex
设置为 flex
,它的 flex-direction
通过 .flex-col
设置为 column
。然后,由于 .mb-4
,它在底部有一些边距。
同时,我们的 <label>
在底部有较少的边距,这要归功于 .mb-2
。其余的类使我们的文本变为大写、加粗、变大(1.125rem
)以及我们颜色调色板中最深的灰色。
总之,为我们的字段设置样式的一种非常快速简便的方法!现在,让我们将这些样式添加到其他字段,为按钮和链接设置样式,看看我们正在处理什么。
<form class="mb-6" action="/" method="post">
<div class="flex flex-col mb-4">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="first_name">First Name</label>
<input class="border py-2 px-3 text-grey-darkest" type="text" name="first_name" id="first_name">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="last_name">Last Name</label>
<input class="border py-2 px-3 text-grey-darkest" type="text" name="last_name" id="last_name">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="email">Email</label>
<input class="border py-2 px-3 text-grey-darkest" type="email" name="email" id="email">
</div>
<div class="flex flex-col mb-6">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="password">Password</label>
<input class="border py-2 px-3 text-grey-darkest" type="password" name="password" id="password">
</div>
<button class="block bg-teal hover:bg-teal-dark text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Create Account</button>
</form>
<a class="block w-full text-center no-underline text-sm text-grey-dark hover:text-grey-darker" href="/login">Already have an account?</a>

添加悬停样式
现在事情开始看起来好多了!在这段代码中,所有内容都应该非常清楚。但是,我们添加了一件新东西:一个状态变体。如果我们看看 <button>
,我们可以看到它的实际应用。
<button class="block bg-teal hover:bg-teal-dark text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Create Account</button>
如果您看看 .bg-teal
之后的类,您会看到我们添加了 hover:
前缀到 .bg-teal-dark
。这些前缀允许我们在悬停和聚焦时设置元素的样式,也允许我们使用断点!总而言之,这是一个非常强大的 Tailwind 功能,它允许我们非常快地创建动态的响应式 UI。
现在,让我们通过将表单定位在屏幕中央并添加一个彩色的页面背景,来完成我们的移动视图。
<div class="flex items-center h-screen w-full bg-teal-lighter">
<div class="w-full bg-white rounded shadow-lg p-8 m-4">
<h1 class="block w-full text-center text-grey-darkest mb-6">Sign Up</h1>
<form class="mb-4" action="/" method="post">
<div class="flex flex-col mb-4">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="first_name">First Name</label>
<input class="border py-2 px-3 text-grey-darkest" type="text" name="first_name" id="first_name">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="last_name">Last Name</label>
<input class="border py-2 px-3 text-grey-darkest" type="text" name="last_name" id="last_name">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="email">Email</label>
<input class="border py-2 px-3 text-grey-darkest" type="email" name="email" id="email">
</div>
<div class="flex flex-col mb-6">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="password">Password</label>
<input class="border py-2 px-3 text-grey-darkest" type="password" name="password" id="password">
</div>
<button class="block bg-teal hover:bg-teal-dark text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Create Account</button>
</form>
<a class="block w-full text-center no-underline text-sm text-grey-dark hover:text-grey-darker" href="/login">Already have an account?</a>
</div>
</div>

砰砰,我们已经拥有了一个美观的移动注册表单!但是,当我们在更大的屏幕上查看它时会发生什么?

响应式设计
它当然比我们普通的 HTML 好,但还需要一些改进。让我们使用一些响应式状态变体,并为更大的屏幕样式化它。
<div class="flex items-center h-screen w-full bg-teal-lighter">
<div class="w-full bg-white rounded shadow-lg p-8 m-4 md:max-w-sm md:mx-auto">
<h1 class="block w-full text-center text-grey-darkest mb-6">Sign Up</h1>
<form class="mb-4 md:flex md:flex-wrap md:justify-between" action="/" method="post">
<div class="flex flex-col mb-4 md:w-1/2">
<label class="mb-2 uppercase tracking-wide font-bold text-lg text-grey-darkest" for="first_name">First Name</label>
<input class="border py-2 px-3 text-grey-darkest md:mr-2" type="text" name="first_name" id="first_name">
</div>
<div class="flex flex-col mb-4 md:w-1/2">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest md:ml-2" for="last_name">Last Name</label>
<input class="border py-2 px-3 text-grey-darkest md:ml-2" type="text" name="last_name" id="last_name">
</div>
<div class="flex flex-col mb-4 md:w-full">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="email">Email</label>
<input class="border py-2 px-3 text-grey-darkest" type="email" name="email" id="email">
</div>
<div class="flex flex-col mb-6 md:w-full">
<label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="password">Password</label>
<input class="border py-2 px-3 text-grey-darkest" type="password" name="password" id="password">
</div>
<button class="block bg-teal hover:bg-teal-dark text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Create Account</button>
</form>
<a class="block w-full text-center no-underline text-sm text-grey-dark hover:text-grey-darker" href="/login">Already have an account?</a>
</div>
</div>

多亏了我们的响应式前缀,我们的注册表单看起来好多了!让我们看看我们的<form>
,找一些例子。
<form class="mb-4 md:flex md:flex-wrap md:justify-between" action="/" method="post">
<!-- ... -->
</form>
就像我们的hover:
前缀一样,我们只在满足条件时才应用带前缀的类。在这种情况下,这意味着我们只在页面min-width
为768px
时才将 flex 样式应用于我们的<form>
。
将实用程序提取到组件
现在我们已经完成了表单的原型设计,我们可以将实用程序类提取到组件类中。让我们从提取我们的<input>
类开始。
<input class="border py-2 px-3 text-grey-darkest" type="password" name="password" id="password">
正如我们所看到的,我们的<input>
上有一些类。我们可以使用 Tailwind 的@apply
指令将这些类提取到我们的 CSS 中。@apply
允许我们将实用程序类使用的相同样式应用于新的类。因此,在我们的styles.css
文件的底部,我们可以添加以下内容
.field {
@apply .border .py-2 .px-3 .text-grey-darkest;
}
然后,在我们重新编译 Tailwind 文件后,我们的<input>
只需要.field
类。
<input class="field" type="password" name="password" id="password">
正如您所看到的,使用 Tailwind,我们获得了实用程序类和组件类的最佳效果!我们可以使用实用程序类快速迭代,并在开始看到模式时仍然提取组件类。
更好的是,我们可以将它们混合起来,处理那些没有意义的专用组件类的情况。
最终 CSS
将这种思想应用于我们代码的其余部分,我们的 CSS 和 HTML 将如下所示。
@tailwind preflight;
/* Here we can add any custom overrides */
.field {
@apply .border .py-2 .px-3 .text-grey-darkest;
}
.field-label {
@apply .uppercase .font-bold .text-lg .text-grey-darkest .mb-2;
}
.field-group {
@apply .flex .flex-col;
}
.btn {
@apply .block .text-white .uppercase .text-lg .p-4 .rounded;
}
.btn-teal {
@apply .bg-teal;
}
.btn-teal:hover {
@apply .bg-teal-dark;
}
.link {
@apply .block .no-underline .text-sm;
}
.link-grey {
@apply .text-grey-dark;
}
.link-grey:hover {
@apply .text-grey-darker;
}
@tailwind utilities;
/* Here we can add our own utilities or custom CSS */
最终 HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tailwind Intro</title>
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="flex items-center h-screen w-full bg-teal-lighter">
<div class="w-full bg-white rounded shadow-lg p-8 m-4 md:max-w-sm md:mx-auto">
<h1 class="block w-full text-center text-grey-darkest mb-6">Sign Up</h1>
<form class="mb-4 md:flex md:flex-wrap md:justify-between" action="/" method="post">
<div class="field-group mb-4 md:w-1/2">
<label class="field-label" for="first_name">First Name</label>
<input class="field md:mr-2" type="text" name="first_name" id="first_name">
</div>
<div class="field-group mb-4 md:w-1/2">
<label class="field-label md:ml-2" for="last_name">Last Name</label>
<input class="field md:ml-2" type="text" name="last_name" id="last_name">
</div>
<div class="field-group mb-4 md:w-full">
<label class="field-label" for="email">Email</label>
<input class="field" type="email" name="email" id="email">
</div>
<div class="field-group mb-6 md:w-full">
<label class="field-label" for="password">Password</label>
<input class="field" type="password" name="password" id="password">
</div>
<button class="btn btn-teal mx-auto" type="submit">Create Account</button>
</form>
<a class="link link-grey w-full text-center" href="/login">Already have an account?</a>
</div>
</div>
</body>
</html>
我们已经提取了重复的类,并保留了其余的。当我们发现自己构建类似的组件时,我们可以提取更多类!
总结
哇!我们在本文中确实涵盖了很多内容。我们从通过构建快速表单来锻炼我们的 HTML 肌肉开始,然后我们使用 Tailwind 对我们的样式采用移动优先的方法。在此过程中,我们看到了如何使用 Tailwind 的实用程序类快速设置元素的样式。然后,我们使用状态变体添加了一些动态样式。最后,我们看到了如何在提取重复类到组件类时既能保持蛋糕又能吃蛋糕。
我希望您能够体验到 Tailwind 如何在您自己的项目中产生影响。如果您想尝试一下这个项目,请随时克隆仓库并自己尝试使用 Tailwind。像往常一样,随时在Twitter上向我提出任何问题。直到下次,祝您编码愉快!
奇怪的是,这种方法(以及许多现代框架方法)颠覆了通常的计算机编程范式。我们通常将人类可读的、更简单的代码编译成机器可读的、更难懂的代码。但在这里你正在做相反的事情 - 尽可能地使用速记,然后将其“编译”成更简单的 CSS 标准。
这里有一个针对所有 Jekyll 用户的入门工具包:https://github.com/taylorbryant/tailwind-jekyll
值得一提的是其他具有类似概念的框架
Inuit.css: https://github.com/inuitcss/inuitcss
Ekzo: https://github.com/ArmorDarks/ekzo
Tachyons: http://tachyons.io/
Basscss: http://basscss.com/
Atomic CSS: https://acss.io/
非常有趣的框架。不过不适合我。在 HTML 中有太多 CSS。这些类中的大多数代表 CSS 属性和值对。这样一来,你几乎就是在做内联样式。
我喜欢 Tailwind,这是一个很棒的概述。一个问题:如果你正在使用~~PHPStorm~~ Visual Studio Code,并且你对红色的波浪线感到厌烦,你会怎么做才能摆脱它们呢?
在 VS Code 中,至少在 PostCSS 有更好的扩展或原生支持之前,你只需要在你的用户设置中添加这个
"css.validate": false
我不会是唯一一个认为这很疯狂的人吧?
你需要一个参考指南才能这样工作……。
还有这个?
我们是在使用原子 CSS,然后将它们组合起来,然后将该类添加到 HTML 中吗?什么?这难道不会违背原子 CSS 的初衷吗?
然后为什么不将其应用于第一个 div 呢?
一致性失败还是我在某个地方弄错了?
类名中的快捷方式一直让我担心。它们通常会使代码库难以理解,除非你精通该系统,而这不仅与原子方法有关。
例如,在Ekzo中,我试图完全避免使用快捷方式。通过巧妙地使用对象,它在很大程度上不会使
class
属性过长。它使这些属性的含义对新手来说保持清晰。例如,这个类名应该非常清楚地表明自己的含义
请注意,像
.text-lg
、.text-grey-darkest
和.mb-2
这样的辅助类实际上并没有直接映射到它们的裸 CSS 对应物,而是作为特定设置起作用。例如,那个large text
是特定值的font-size
和line-height
,那个grey darkest
是一个非常具体的颜色,margin bottom
辅助类很可能是基于一个特定的垂直节奏值。如果我们把它们想象成 mixin 或变量,那么更容易理解它们实际上封装了我们项目中预定义的规则,并允许我们在任何地方重用它们。总的来说,我认为 Tailwind CSS 确实存在问题。它缺乏抽象层,过于依赖组合。
这个问题可以通过 Inuit.css 或 Ekzo 或其他原子框架更好地解决,这些框架不仅提供了裸辅助类,还提供了更完整的,但仍然是无设计的元素 - 对象(在不同的方法中命名不同)。然后你主要使用对象来构建输出,这样会更简洁。辅助类仅用于调整这些对象或其他实例,这些实例需要很少的、没有模式的调整。这样你就永远不会遇到这样的情况,即 DOM 元素中只有辅助类,而没有任何加载,但仍然保持很大的灵活性,并且能够在不编写任何 CSS 代码的情况下快速构建输出,也不必为要设置样式的每个 DOM 元素发明类名。这毕竟可以节省大量时间。
这是一个使用更面向抽象的方法的布局示例。在这里,辅助类仅用于调整或构建非常原始的东西,这些东西不值得为它们编写完整的 CSS 代码,也不存在明确的模式,这些模式无法提取到一个对象中。
我理解为什么这会让你有点摸不着头脑。正如 Tailwind 的其中一位创建者所说,基本的想法是,通过使用 BEM 这样的方法,我们正在进行很多过早的优化。我们假设所有东西都需要组件化并且会重复使用。然而,在实践中,这很快就会失控。
使用 BEM,我发现自己总是要创建新的“修饰符”类,仅仅为了更改组件在某个特定实例上的填充或边距。
然后就是命名问题(我讨厌命名东西)。
Tailwind 的方法
– 直接开始编写标记并使用实用程序类构建页面
– 当你发现自己一遍又一遍地复制粘贴相同的类时,只需复制这些类并从中提取一个组件(例如上面的 .container 示例)。
– 如果需要调整组件某个特定实例的边距或填充,则无需编写新的 CSS,而是使用已有的实用程序类。
我建议你尝试用它构建一些东西。我最初对此持怀疑态度,但现在我非常喜欢它。
Tailwind 的创建者之一 Adam Wathan 对使用实用程序类做出了很好的论证:https://adamwathan.me/css-utility-classes-and-separation-of-concerns/.
你用特定 DOM 元素的唯一声明换取了类汤。这不是一件好事。
看起来类太多了,我认为在唯一声明和辅助类之间保持健康的平衡是理想的。
看看有多少代码噪音。对于一个基本的表单来说也是如此。除了小型项目之外,没有任何项目可以采用这种方法并进行扩展。CSS 的名称中包含“级联”,这种方法是垃圾。
这不是未来。这是过去。
为什么不直接编写内联 CSS 呢?
因为内联样式不会强制一致性。例如,通过允许在任何地方使用
font-size
,你的开发人员将在一个地方放置font-size: 10px
,而在另一个地方放置font-size: 11px
。通过为他们提供一组预定义的类,他们只能使用.font-size-sm
来表示小尺寸,这将准确地封装font-size: 10px
,甚至可能包含其所需的 line-height。将它们视为 mixin 或常量。