一文入坑 Tailwind CSS

发布于:2024-05-09 ⋅ 阅读:(30) ⋅ 点赞:(0)

本文记录在前端项目中使用 Tailwind CSS 进行快速原型开发的过程。首先是 Tailwind CSS 开发环境的配置,在此基础之上配置 Prettier 使开发过程更加流畅。最后,通过实际案例展示 Tailwind CSS 的丝滑使用。

1. 工程化配置

这一段直接参考官方网站的推荐配置就可以了,笔者已经试过了,完全没有卡点,请放心使用。官方网站传送门:

1.1. 创建一个新目录

mkdir demo && cd demo && npm init -y

1.2 安装必要的依赖

npm install -D tailwindcss
npx tailwindcss init

这句话执行完毕之后,会在项目目录下面自动创建出一个 tailwind.config.js 文件,这就是 Tailwind CSS 的配置文件。

这个文件目前长这样:

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

我们需要指定 content 的字段的值,改造完毕之后长成这样:

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

1.3 添加必要的文件

使用下面的命令在项目中创建必要的文件:

mkdir src
touch src/index.html src/input.css

这两个文件的内容分别如下所示:

@tailwind base; 
@tailwind components; 
@tailwind utilities;
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link href="./output.css" rel="stylesheet" />
  </head>
  <body>

  </body>
</html>

1.4 output.css

细心的你一定发现了,我们创建了名为 input.css 的文件,但是在 html 文件中引入的却是 output.css 文件。因此,我们需要根据 input.css 编译得到 output.css 文件。

在终端运行下面的命令:

npx tailwindcss -i ./src/input.css -o ./src/output.css --watch

1.5 试运行

到此 Tailwind CSS 的基本配置就完成了!现在让我们试一试吧:

<h1 class="text-3xl font-bold underline"> Hello world! </h1>

效果如下:

image.png

image.png

完美!

1.6 配置 prettier

如果您之前对这种原子化的样式有所了解的话,可以预见的是在接下来的工作中,我们会大量的使用一些预制的类名。由此会产生一个显而易见的问题:

如果这些类名真的很多的话,在书写的时候如果没有良好的习惯就会对后续阅读造成极大的困扰。因此,这些预制的原子化的类名必须要有一个大致的规则。这个时候就应该让 prettier 上台表演了。

下面开始介绍配置 Prettier。

参考官方给出的步骤:

  1. 在 vscode 中安装 prettier 插件。这个插件的名称为:Prettier - Code formatterv10.4.0
  2. 然后在项目控制台运行下面的命令 npm install -D prettier prettier-plugin-tailwindcss
  3. 在项目的根目录下创建配置文件 touch .prettierrc
{
  "plugins": ["prettier-plugin-tailwindcss"]
}

到此为止,我们已经配置好了 Prettier, 你可以使用 shift+alt+f 来格式化你选中的代码,如下所示: 格式化前:

image.png

格式化之后:

image.png

注意看 class 的顺序发生了变化,而这就是我们想要的效果。

2. 熟悉 TailWind CSS

在这个小节中,让我们来纯享一下敲代码的乐趣,通过这个过程,对原子化的样式或者 Tailwind CSS 的基本风格来一个全面的、直观上的熟悉。

不过在此之前需要介绍一些学习工具:

  1. 在线编辑器: 如果你不想搭建如上面所示的复杂的环境,你可以直接在这个在线沙盒上体验 Tailwind CSS 的编写。
  2. 调色板: 用来产生 Tailwind CSS 的类名和子类名。

2.1 写一个盒子

<div
  class="h-12 w-12 bg-green-500"
></div>

<div
  class="h-12 w-12 bg-red-500"
></div>

<div
  class="h-12 w-12 bg-blue-500"
></div>

image.png

2.2 invisible 和 hidden

在 Tailwind 中 invisible 指的是 visible: hidden 而 hidden 指的是 display: none。请一定要注意这个区别,因为它们用的单词都比较相似,所以很容易搞混。

<div
  class="invisible h-12 w-12 bg-green-500"
></div>
<div
  class="inline h-12 w-12 bg-red-500"
>okk</div>
<div
  class="hidden h-12 w-12 bg-blue-500"
>123</div>
<div
  class="inline h-12 w-12 bg-blue-500"
>996</div>

image.png

2.3 flex 布局

flex 布局使用的频率相当之高,因此下面让我们尝试使用 Tailwind 来布置弹性盒子吧!

<div class="flex flex-col">
  <div class="h-12 w-12 bg-green-500">Hello</div>
  <div class="h-12 w-12 bg-red-500">My</div>
  <div class="h-12 w-12 bg-blue-500">Friend</div>
</div>

image.png

<div class="flex flex-row justify-center">
  <div class="h-12 w-12 bg-green-500">Hello</div>
  <div class="h-12 w-12 bg-red-500">My</div>
  <div class="h-12 w-12 bg-blue-500">Friend</div>
</div>

居中对齐:

<div class="flex flex-row justify-center h-screen items-center">
  <div class="h-12 w-12 bg-green-500">Hello</div>
  <div class="h-12 w-12 bg-red-500">My</div>
  <div class="h-12 w-12 bg-blue-500">Friend</div>
</div>

image.png

2.4 grid 布局

grid 布局用到的不多,这里简单提一下。

<div class="h-screen grid grid-cols-3">  
    <div class="h-12 w-12 bg-green-500">Hello</div>  
    <div class="h-12 w-12 bg-red-500">My</div>  
    <div class="h-12 w-12 bg-blue-500">Friends</div>  
    <div class="h-12 w-12 bg-green-500">Hob</div> <!-- 假设"Hob"是你要表达的内容,否则可能需要更正 -->  
    <div class="h-12 w-12 bg-red-500">My</div>  
    <div class="h-12 w-12 bg-blue-500">Friend</div>  
    <div class="h-12 w-12 bg-green-500">Hello</div>  
    <div class="h-12 w-12 bg-red-500">My</div>  
    <div class="h-12 w-12 bg-blue-500">Friend</div>  
    <div class="h-12 w-12 bg-green-500">Hello</div>  
    <div class="h-12 w-12 bg-red-500">My</div>  
    <div class="h-12 w-12 bg-blue-500">Friend</div>  
</div>

效果如下:

image.png

2.5 Tailwind 中的响应式

在 Tailwind 中实现页面的响应式是非常简单的。Tailwind CSS 提供了一种叫做 Breakpoint 的概念来实现响应式,这也是 Tailwind 比起其它如 Bootstrap Chakra UI 库更加优秀的地方。

本质上来说, Breakpoint 实际上是一个 State , 它反映出的实际上是页面的一种状态。类似于媒体查询,下面是这 5 种 Breakpoint:

image.png

sm 举例,sm:h-12 表示的含义为当视口的宽度在 640px 以上的时候 h-12 类名生效!

需要注意的是,Tailwind CSS 种有一个 mobile first 的理念,这很重要;也从另一个角度可以看出,使用 Tailwind 实现响应式是比较简单和被推崇的!

下面举个例子:

<div class="text-4xl"></div>
<div class="mmd:text-red-500 invisible sm:visible">sm</div>
<div class="invisible md:visible">md</div>
<div class="invisible lg:visible">lg</div>
<div class="invisible xl:visible">xl</div>
<div class="invisible 2xl:visible">2xl</div>

效果如下:

sm.gif

根据这些 Breakpoint , 我们实现一个具有实际意义的响应式效果:

<div class="flex flex-col text-4xl sm:flex-row">  
  <div class="">base</div>  
  <div class="">sm</div>  
  <div class="">md</div>  
  <div class="">lg</div>  
  <div class="">xl</div>  
  <div class="">2xl</div>  
</div>
image.png

屏幕的宽度逐渐变宽:

image.png

2.6 制作一个按钮

<button class="m-4 rounded-md bg-red-500 px-4 py-2 text-white ring-green-500 hover:bg-red-600 focus:ring-2 active:bg-red-400 sm:bg-indigo-500 sm:hover:bg-indigo-600 sm:active:bg-indigo-400">Click me</button>

image.png

hover 的时候的样式:

image.png

active 的时候的样式:

image.png

代码解释:

  • m-4: 外边距(margin)大小为4,根据Tailwind的spacing scale,这通常是1rem或者16px。
  • rounded-md: 添加中等大小的圆角。
  • bg-red-500: 背景颜色,使用红色调的500色阶,这是Tailwind预设的颜色之一。
  • px-4: 水平内边距(padding)大小为4,通常是16px。
  • py-2: 垂直内边距(padding)大小为2,通常是8px。
  • text-white: 文本颜色设置为白色。
  • ring-green-500: 添加一个绿色调的500色阶的边框(ring),通常用于表单元素获得焦点时的样式。
  • hover:bg-red-600: 鼠标悬停时背景颜色变为红色调的600色阶。
  • focus:ring-2: 获得焦点时,增加一个边框的大小到2,通常是8px。
  • active:bg-red-400: 鼠标按下激活时背景颜色变为红色调的400色阶。
  • sm:bg-indigo-500: 在小屏幕(sm)设备上,背景颜色为靛蓝色调的500色阶。
  • sm:hover:bg-indigo-600: 在小屏幕设备上鼠标悬停时,背景颜色变为靛蓝色调的600色阶。
  • sm:active:bg-indigo-400: 在小屏幕设备上鼠标按下激活时,背景颜色变为靛蓝色调的400色阶。

2.7 更加复杂的选择器

Tailwind CSS 提供了类似于伪类的选择器,例如可以循环渲染的时候选择第一个标签生效,当然也可以选择偶数或者奇数序列的 item 生效。

<ul>
  <li class="first:bg-green-400 last:bg-blue-400 odd:bg-purple-400 even:bg-yellow-300">Element 1</li>
  <li class="first:bg-green-400 last:bg-blue-400 odd:bg-purple-400 even:bg-yellow-300">Element 2</li>
  <li class="first:bg-green-400 last:bg-blue-400 odd:bg-purple-400 even:bg-yellow-300">Element 3</li>
</ul>

image.png

2.8 自定义样式

如下图所示,如果我想要设置 px-13 , 但是没有预置这个类名,那么我应该如何做呢? image.png 这个时候使用一对中括号就可以了:

image.png

2.9 复用自定义样式

如果需要重用自定义的一些样式或者设置一些自己的变量使之能够在全局中使用,我们通常将这类行为称之为:主题设置。

显然, Tailwind CSS 在一开始就提供了 tailwind.config.js 文件进行配置。而在在线的沙盒环境中,我们可以参考如下的图进行配置:

image.png 然后, image.png

上面的步骤里,我们在配置文件中就成功的配置了一个叫做 bg-mint 的类名,它是可以在全局中使用的。

更进一步,我们可以配置多个字类名:

image.png

在 中快速生成:

image.png image.png

当然也可以设置默认类名:

/** @type {import('tailwindcss').Config} */
export default {
  theme: {
    extend: {
      colors: {
        mantis: {
          DEFAULT: '#61a146',
          50: '#f6faf3',
          100: '#e9f5e3',
          200: '#d3eac8',
          300: '#afd89d',
          400: '#82bd69',
          500: '#61a146',
          600: '#4c8435',
          700: '#3d692c',
          800: '#345427',
          900: '#2b4522',
          950: '#13250e',
        },
      },
    },
  },
  plugins: [],
}
image.png

更多例子:

image.png image.png

3. 定制化

3.1 预制样式的定制化

还记得第一小节中的 input.css 吗?它其中的内容为:

@tailwind base;
@tailwind components;
@tailwind utilities;

基于此文件编译得到了 output.css 并在 html 文件中引用。然后我们就看到 Tailwind CSS 生效了。

基于这样的事实,我们不难得到结论,那就是:我们可以在这个文件中进行定制化。而在 Tailwind 提供的在线沙盒环境中,也有这样的文件,如下图所示:

image.png

下面让我们开始配置我们的 Tailwind 吧! 开始的css

@tailwind base;
@tailwind components;
@tailwind utilities;

的作用是可以将一些预置的样式去除,如下所示:

image.png

可以清楚的看到 无论是 h1 p h2 它们展示出来的效果是完全一样的。也就是说浏览器预制的样式被移除了。这主要是为了防止样式冲突,也就是 Tailwind 的原子化样式和浏览器预制样式之间的冲突。

所以,我们需要根据自己的需要给已经去除预制样式的这些标签重新设置预制样式,如下所示:

@tailwind base; 
@tailwind components; 
@tailwind utilities; 

@layer base { 
  h1{
    @apply text-4xl font-bold; 
  } 
  h2 { 
    @apply text-2xl font-semibold; 
    background-color: red; 
  }
  p {
    @apply text-gray-400;
  }
}

效果如下:

image.png

上述代码中,我们通过给 @layer base 中诸如了一些自定义代码实现样式预制。可以看到预制样式通过两种方式混合完成:

  1. 通过 @apply Tailwind Cls
  2. 直接使用 CSS 的原生样式

这两者是可以混用的,这很重要!

3.2 自定义组件样式

base 中可以注入定制样式,那么同样可以给 components 或者 utilities 中注入自定义代码。事实上,通过给 components 注入自定义样式可以完成自定义组件的功能。

<button class="rounded-lg bg-red-500 px-4 py-2 text-white hover:bg-red-600">Delete</button> <button class="rounded-lg bg-red-500 px-4 py-2 text-white hover:bg-red-600">Danger</button>

上述样式如下所示:

image.png

通过自定义的配置:

@layer components {
  .btn-danger {
    @apply rounded-lg bg-red-500 px-4 py-2 text-white hover:bg-red-600;
  }
}

这个时候只需要直接使用预定义的类名就可以了:

<button class="btn-danger">Delete</button>  
<button class="btn-danger">Danger</button>

对比一下:

image.png

3.3 自定义快捷样式

有时候,在项目中会大量的用到样式组合,这个时候,我们可以将一组特定的样式搭配组合起来使用,就像 Less 中的 Mixin 一样:

<div class="flex items-center justify-center h-44 bg-blue-200"
  <p>HI</p>
</div>

通过配置:

@layer directives {
  .flex-center {
    @apply flex items-center justify-center;
  }
}

就可以简写成:

<div class="flex-center h-44 bg-blue-200"
  <p>HI</p>
</div>

非常好用!

细心的你一定发现了,这样一来 @layer components@layer directives 有什么区别呢?

实际上,没有任何区别,它们之所以写在不同的 block 中仅仅是为了可读性

4. 暗色模式切换

在 Tailwind 中切换主题色是非常简单的事情,首先需要在配置文件中指定切换模式是采用 class Name:

/** @type {import('tailwindcss').Config} */
export default {
  darkMode: "class",
  theme: {
    extend: {
      // ...
    },
  },
  plugins: [],
}

也就是上述代码中的:darkMode: "class",

接下来通过一个案例说明:

<div class="flex h-screen items-center justify-center">  
  <div class="rounded-lg bg-white p-6 shadow-lg dark:bg-indigo-950 dark:shadow-sm">  
    <h2 class="text-2xl font-semibold text-gray-800 dark:text-white">Card Title</h2>  
    <p class="mt-2 text-gray-600 dark:text-gray-400">This is the main content of the card.</p>  
    <p class="mt-2 text-gray-400 dark:text-gray-200">Subtext or additional information goes here.</p>  
    <button onclick="document.documentElement.classList.toggle('dark')" class="mt-4 rounded bg-indigo-500 px-4 py-2 font-semibold text-white hover:bg-indigo-600">Button</button>  
  </div>  
</div>

点击之前:

image.png

点击之后:

image.png

总结一下:

  1. 使用 darkMode: "class", 设置主题色的切换方式。
  2. 使用 dark:*** 作为暗色模式下的状态表示。

5. 在工程化项目中使用 Tailwind CSS

参考下面的地址完成 Vite + React 项目的创建:. 步骤如下:

  1. 使用 create vite 创建新的项目:
npm create vite@latest my-project -- --template react
cd my-project
  1. 安装 Tailwind CSS 依赖
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
  1. 修改配置文件 tailwind.config.js 的内容
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
  1. 修改主样式文件 ./src/index.css 中的内容
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. 将启动端口号修改为3000
export default defineConfig({
  plugins: [react()],
  server: {
    port: 3000,
  }
})
  1. 修改 App.jsx 文件中的内容
export default function App() {
  return (
    <h1 className="text-3xl font-bold underline">
      Hello world!
    </h1>
  )
}
  1. 启动项目并观察效果
npm run dev

效果如下: image.png

  1. 安装插件配置 prettier 为了能够自动格式化 Tailwind 的类名,在 vscode 中安装名为 Tailwind CSS IntelliSense 的插件。为了配合插件和能够直接引入 SVG,安装如下的依赖:
npm i prettier prettier-plugin-tailwindcss vite-plugin-svgr --save

然后使用编辑器中的快捷键 ctrl+shift+p 输入 settings.json 然后确保:"formatOnSave":true, 已经被设置了。

如此一来,只需要保存代码就会自动格式化了。

6. 使用 Tailwind CSS 完成第一个组件

在前端项目中常常会用到大量的 icon ,为此在正式开始之前,我们需要先安装一个插件和一个 icon 组件库:

  1. 安装 react-icons@^4.11.0 组件库,方便 icon 使用
  2. 安装 react icons auto import@v0.0.7 插件

image.png

如上图所示,悬浮显示 import 点击之后就会在当前文件中自动引入该 icon。

import { AiFillAlert } from "react-icons/ai"; 

好的。在这个小节中,我们使用 Tailwind CSS 小试牛刀一把。创建一个 React 的 Nav 组件,代码如下所示:

import { TbShoppingBag } from "react-icons/tb";
import NikeLogo from "../assets/nike-logo.svg?react";
import { RxHamburgerMenu } from "react-icons/rx";
import { useState } from "react";

const ROUTES = [
  "Home",
  "About",
  "Services",
  "Pricing",
  "Contact",
];
export function Nav({ onClickShoppingBtn }) {
  const [isMobileMenuShown, setIsMobileMenuShown] =
    useState(false);
  return (
    <nav className="relative z-10 flex flex-wrap items-center justify-between">
      {/* Logo */}
      <a href="#">
        <NikeLogo className="h-20 w-20 dark:fill-white" />
      </a>

      {/* Burger button */}
      <button
        onClick={() => setIsMobileMenuShown(!isMobileMenuShown)}
        className="rounded-lg p-2 hover:bg-gray-100 focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 lg:hidden"
      >
        <RxHamburgerMenu size={25} />
      </button>

      {/* Menu list */}
      <div
        className={`${
          isMobileMenuShown === false && "hidden"
        } w-full lg:block lg:w-auto`}
      >
        <ul className="flex flex-col rounded-lg border border-gray-100 bg-gray-50 p-4 text-lg lg:flex-row lg:space-x-8 lg:border-none lg:bg-transparent lg:dark:text-white">
          {ROUTES.map((route, i) => {
            return (
              <li
                className={`cursor-pointer rounded px-3 py-2 lg:hover:bg-transparent lg:hover:text-blue-500 ${
                  i === 0
                    ? "bg-blue-500 text-white lg:bg-transparent lg:text-blue-500 "
                    : "hover:bg-gray-100"
                } ${(i == 3 || i == 4) && "lg:text-white"}`}
                key={route}
              >
                <a>{route}</a>
              </li>
            );
          })}
        </ul>
      </div>

      {/* Cart button */}
      <div
        onClick={onClickShoppingBtn}
        className="btn-press-anim fixed bottom-4 left-4 lg:static lg:mr-8"
      >
        <div className="flex-center h-12 w-12 cursor-pointer rounded-full bg-white shadow-md">
          <TbShoppingBag />
        </div>
      </div>
    </nav>
  );
}

代码解释: 这段代码是一个使用React和Tailwind CSS框架的导航组件。以下是代码中各个类名的解释和如何实现响应式效果的说明:

类名解释

  1. relative: 使元素的定位上下文为相对定位。
  2. z-10: 设置元素的堆叠顺序(z-index),值为10。
  3. flex: 启用Flexbox布局。
  4. flex-wrap: 允许子元素换行。
  5. items-center: 垂直居中对齐子元素。
  6. justify-between: 子元素在主轴上分隔开来。
  7. rounded-lg: 为元素添加大圆角。
  8. p-2: 设置元素的内边距为2个单位。
  9. hover:bg-gray-100: 鼠标悬停时背景颜色变为灰色100。
  10. focus:ring-2: 获得焦点时,添加一个2个单位大小的边框。
  11. focus:ring-gray-200: 获得焦点时,边框颜色为灰色200。
  12. dark:text-gray-400: 在暗色模式下,文本颜色为灰色400。
  13. dark:hover:bg-gray-700: 在暗色模式下,鼠标悬停时背景颜色为灰色700。
  14. lg:hidden: 在大屏幕设备上隐藏元素(响应式)。
  15. w-full: 宽度为100%。
  16. lg:block: 在大屏幕设备上显示为块级元素(响应式)。
  17. lg:w-auto: 在大屏幕设备上宽度自动(响应式)。
  18. lg:flex-row: 在大屏幕设备上子元素水平排列(响应式)。
  19. lg:space-x-8: 在大屏幕设备上子元素水平间距为8个单位(响应式)。
  20. lg:border-none: 在大屏幕设备上移除边框(响应式)。
  21. lg:bg-transparent: 在大屏幕设备上背景透明(响应式)。
  22. lg:dark:text-white: 在大屏幕设备和暗色模式下文本颜色为白色(响应式)。
  23. cursor-pointer: 鼠标悬停时显示指针光标。
  24. rounded: 添加圆角。
  25. px-3: 设置元素的水平内边距为3个单位。
  26. py-2: 设置元素的垂直内边距为2个单位。
  27. lg:hover:bg-transparent: 在大屏幕设备上鼠标悬停时背景透明(响应式)。
  28. lg:hover:text-blue-500: 在大屏幕设备上鼠标悬停时文本颜色变为蓝色500(响应式)。
  29. btn-press-anim: 可能是一个自定义类,用于添加按钮按下的动画效果。
  30. fixed: 固定定位。
  31. bottom-4: 底部距离为4个单位。
  32. left-4: 左侧距离为4个单位。
  33. lg:static: 在大屏幕设备上取消固定定位(响应式)。
  34. lg:mr-8: 在大屏幕设备上右侧外边距为8个单位(响应式)。

我们在项目中的 index.css 文件中预先定义了 flex-center 这个类名:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  body {
    font-family: "Nunito Sans Variable", sans-serif;
  }
}

@layer utilities {
  .flex-center {
    @apply flex items-center justify-center;
  }
  .btn-press-anim {
    @apply transition active:scale-75;
  }
}

响应式效果实现

响应式效果主要通过添加带有屏幕尺寸前缀的类来实现,例如:

  • lg: 表示在大屏幕设备上(通常大于1024px宽)的样式。
  • dark: 表示在暗色模式下的样式。

当屏幕尺寸变化或系统主题变为暗色模式时,这些响应式类会应用,从而改变元素的样式以适应不同的视口大小和主题。

例如,lg:hidden 类在大屏幕上不会隐藏元素,但在小屏幕上会隐藏。dark:text-gray-400 类在暗色模式下会改变文本颜色。

通过这种方式,开发者可以创建适应不同屏幕尺寸和主题的动态用户界面。

7. 引入新字体

在 Tailwind CSS 框架下面引入一个新的字体是非常简单的,本节就以 Nunito Sans variable 为例,叙述引入新字体的步骤。

  1. 安装字体依赖
npm install @fontsource-variable/nunito-sans@^5.0.9
  1. 在入口文件中引入此字体
import "@fontsource-variable/nunito-sans";
  1. 在样式入口文件中配置此字体
@tailwind base;
@tailwind components;
@tailwind utilities;

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  body {
    font-family: "Nunito Sans Variable", sans-serif;
  }
}
  1. 使用效果对比

使用之前:

image.png

使用之后:

image.png

这样就生效了,并且由于是在 index.css 中进行的配置,所以这个字体会被默认当成首选字体。

8. 使用渐变色

在 Tailwind CSS 中使用 class 设置渐变色彩有些难以想象,这小节直接给出样例。既然是规则,开发者在绘制页面的时候只需要遵守就可以了,无需在这个上面投入太多感情。

  <div class="h-40 w-40 bg-gradient-to-br from-[#F637CF] from-5% via-[#E3D876] via-50%  to-[#4DD4C6] 95%">
  </div>  

效果如下:

image.png

简单来说就是三个阶段,七个关键词:

  1. 设置颜色的方向bg-gradient-to-br 这里的 br 指的就是 bottom-right。
  2. 起始阶段 from: from-[#F637CF]from-5% 表示开始时颜色和色卡的位置。
  3. 过渡阶段 via : via-[#E3D876]via-5% 表示中间时颜色和色卡的位置。
  4. 结束阶段 to: to-[#4DD4C6]to-5% 表示结束时颜色和色卡的位置。

再举一个例子巩固一下:

<button type="button" class="bg-gradient-to-r from-green-400 to-blue-500 hover:from-pink-500 hover:to-yellow-500 rounded-lg p-2 m-2">
  Hover me
</button>
image.png image.png

9. 创建一个 Select 组件

代码如下:

import { IoIosArrowDown } from "react-icons/io";
import { twMerge } from "tw-merge";
export function Select({
  title,
  options,
  className,
  onChange,
  value,
}) {
  return (
    <div className="relative dark:text-black">
      <select
        onChange={(e) => onChange(e.target.value)}
        value={value || ""}
        className={twMerge(
          `w-24 appearance-none border border-gray-300 bg-white p-4  ${className}`,
        )}
      >
        <option value="" disabled hidden>
          {title}
        </option>
        {options.map((option) => (
          <option value={option} key={option}>
            {option}
          </option>
        ))}
      </select>
      <div className="flex-center pointer-events-none absolute inset-y-0 right-0 pr-3">
        <IoIosArrowDown />
      </div>
    </div>
  );
}

代码解释:

  1. relative: 为父元素设置相对定位,这通常用于子元素的绝对定位参考。
  2. dark:text-black: 在暗黑模式下设置文本颜色为黑色。
  3. w-24: 设置元素的宽度为24单位(通常是24px,取决于Tailwind的基础单位设置)。
  4. appearance-none: 移除浏览器的默认外观,常用于自定义表单控件的样式。
  5. border: 添加边框。
  6. border-gray-300: 设置边框颜色为gray-300(一种较浅的灰色)。
  7. bg-white: 设置背景颜色为白色。
  8. p-4: 设置内边距为4单位(通常是4px的倍数,取决于Tailwind的基础单位设置)。
  9. {className}: 这是一个占位符,表示可以动态传入其他类名。
  10. flex-center: 这个类名不是Tailwind的默认类名,可能是自定义的,通常用于快速居中对齐flex子项(可能是justify-centeritems-center的组合)。
  11. pointer-events-none: 设置元素不响应鼠标、触摸等指针事件,这样点击该元素时不会触发任何动作。
  12. absolute: 设置绝对定位。
  13. inset-y-0: 设置元素的顶部和底部内边距为0,常用于绝对定位元素的垂直对齐。
  14. right-0: 设置元素的右边界与其相对定位的父元素的右边界对齐。
  15. pr-3: 设置元素的右边内边距为3单位。

除此之外,组件中还有一个关于设置 option 的技巧:

  • 使用disabled属性可以禁用一个option,使其不可选。这通常用于显示一个提示性的标题或占位符,就像代码中的{title}那样。
  • 使用hidden属性可以隐藏一个option,使其在下拉列表中不可见。这通常与disabled一起使用,以隐藏并禁用标题或占位符选项。
  • value属性用于设置option的值,当这个option被选中时,它将作为<select>元素的值。
  • 为了确保可访问性和用户体验,最好为每个option提供一个明确的标签(即option元素的文本内容),以便用户了解每个选项的含义。
  • 当动态生成option元素时(如使用map函数),确保为每个option提供一个唯一的key属性,以帮助React识别哪些项发生了变化、被添加或被移除。这有助于提高渲染性能并防止不必要的重新渲染。

10. 使用动画

Tailwind CSS 内置了一些动画,比如:

image.png

但是显而易见的是,在很多时候我们需要定义自己想要的动画,而定义这些动画就需要在 tailwind.config.js 配置文件中进行配置了。

配置的代码算不上麻烦,只需要记住规则就好了,如果及不知的话,可以在下面这个网站中进行查询:

/** @type {import('tailwindcss').Config} */
export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  darkMode: "class",
  theme: {
    extend: {
      colors: {
        night: {
          DEFAULT: "#0D1120",
          50: "#171E2C",
          500: "#0D1120",
        },
      },
      keyframes: {
        wiggle: {
          "0%,100%": { transform: "rotate(-3deg)" },
          "50%": { transform: "rotate(3deg)" },
        },
        float: {
          "0%, 100%": { transform: "translateY(0)" },
          "50%": { transform: "translateY(-10px)" },
        },
        fadeIn: {
          "0%": { opacity: "0" },
          "100%": { opacity: "1" },
        },
      },
      animation: {
        wiggle: "wiggle 1s ease-in-out infinite",
        float: "float 4s ease-in-out infinite",
        fadeIn: "fadeIn 1s ease-in-out",
      },
    },
  },
  plugins: [],
};

上面三个代码我们就很容易的配置了三个自定义动画,这个时候就可以直接使用了:

image.png

不难看出,在 Tailwind 中自定义一个动画其实只需要两步,首先是在 extend 的 keyframes 中定义好关键帧动画,这个和在 CSS 中的写法是完全一样的。然后在 animation 中定义动画,其实写法也和在 CSS 中一样。

11. 商品组件

接下来,让我们使用 Tailwind 完成一个烂大街的商品详情组件,代码如下:

import { useState } from "react";
import { QTY, SIZES } from "../constant";
import { Select } from "./Select";

export function ShoeDetail({ shoe, onClickAdd }) {
  const [form, setForm] = useState({ qty: null, size: null });
  return (
    <div className="flex flex-col space-y-4 dark:text-white lg:flex-row-reverse">
      {/* Shoe image */}
      <div className="flex-1 lg:-mt-32 lg:ml-28">
        <div className="flex-center  h-full bg-gradient-to-br from-[#F637CF] from-5% via-[#E3D876] via-40% to-[#4DD4C6]">
          <img className="animate-float" src={shoe.src} />
        </div>
      </div>
      <div className="flex-1 space-y-6">
        {/* Shoe text details */}
        <div className="text-5xl font-black md:text-9xl">
          {shoe.title}
        </div>
        <div className="font-medium md:text-xl">
          {shoe.description}
        </div>
        <div className="flex space-x-6">
          <div className=" text-3xl font-extrabold md:text-6xl">
            {shoe.price} $
          </div>
          <Select
            value={form.qty}
            onChange={(qty) => setForm({ ...form, qty })}
            title={"QTY"}
            options={QTY}
          />
          <Select
            value={form.size}
            onChange={(size) => setForm({ ...form, size })}
            title={"SIZE"}
            options={SIZES}
          />
        </div>
        {/* Shoe buttons and links */}
        <div className="space-x-10">
          <button
            onClick={() => onClickAdd(shoe, form.qty, form.size)}
            className="btn-press-anim h-14 w-44 bg-black text-white hover:bg-gray-900 active:bg-gray-700 dark:bg-white  dark:text-black"
          >
            Add to bag
          </button>
          <a
            href="#"
            className="text-lg font-bold underline underline-offset-4"
          >
            View details
          </a>
        </div>
      </div>
    </div>
  );
}

这段代码定义了一个React组件ShoeDetail,这个组件负责展示鞋子的详细信息,并提供添加购物车和查看详情的功能。下面是对这个组件功能的详细解释:

  1. 状态管理:组件内部使用useState来管理用户选择的数量(qty)和尺码(size)。这两个状态将被用于添加到购物车的操作。

  2. 组件结构:整个组件的布局使用Flexbox进行排版,主要分为两大部分:一部分是鞋子的图片,另一部分是鞋子的详细信息及操作按钮。

  3. 鞋子图片:在<div>容器中展示了一张鞋子的图片,背景有一个渐变效果,图片则应用了animate-float类名,可能是一个自定义的动画效果。

  4. 鞋子文本详情:包括鞋子的标题(大字体突出显示)、描述、价格。价格后面紧跟两个下拉选择框,分别用于选择购买数量和鞋子尺码。这两个下拉框使用的是之前导入的Select组件。

  5. 操作按钮:提供了“Add to bag”按钮用于将鞋子添加到购物车,点击该按钮时会触发传入的onClickAdd回调函数,并将鞋子信息、选择的数量和尺码作为参数传递。同时,还提供了一个“View details”的链接,可能是用于跳转到鞋子的详细页面。

  6. 响应式设计:组件中使用了多个Tailwind CSS的响应式断点类名(如lg:flex-row-reversemd:text-9xl等),以确保在不同屏幕尺寸下都能有良好的布局和显示效果。

  7. 可访问性与动画:代码中包含了一些增强用户体验的元素,如animate-float可能为图片添加了浮动动画效果,btn-press-anim可能为按钮添加了点击动画效果。同时,通过下划线偏移(underline-offset-4)改善了文本链接的可视效果。

总结一下,这个ShoeDetail组件是一个典型的产品详情页组件,它结合了状态管理、响应式设计、用户交互和动画效果,为用户提供了一个直观且吸引人的购物体验。

12. 卡片组件

让我们趁热打铁,完成一个常见的卡片组件。

export function Card({ item, onClick }) {
  return (
    <div
      onClick={() => onClick(item)}
      className={`${item.className} max-w-xl transform cursor-pointer transition hover:scale-105`}
    >
      <div className="p-8">
        <div className="text-2xl font-bold">{item.title}</div>
        <div className="mt-10 font-semibold underline underline-offset-4">
          SHOP NOW +
        </div>
      </div>
      <img
        className="absolute left-[50%] top-5 h-40"
        src={item.src}
      />
    </div>
  );
}

当然,让我们分析这个React组件中使用的Tailwind CSS类名:

  1. max-w-xl:

    • max-w-xl: 设置元素的最大宽度为“extra large”,即一定的像素宽度限制,通常用于限制容器或元素的宽度,防止其变得过宽。
  2. transform:

    • transform: 允许你对元素应用2D或3D转换效果。在这个上下文中,它可能是为了使hover:scale-105效果能够生效,因为这个效果是一个转换。
  3. cursor-pointer:

    • cursor-pointer: 当你把鼠标移到元素上时,光标会变成一个手形指针,通常用于表示一个可点击的元素。
  4. transition:

    • transition: 为元素上的某些属性提供平滑的过渡效果。在这里,它可能是为了使hover:scale-105的放大效果更加平滑。
  5. hover:scale-105:

    • hover:scale-105: 当用户将鼠标悬停在元素上时,元素会稍微放大(增加到其原始大小的105%)。这是一个常见的交互效果,用于提供用户反馈。
  6. p-8:

    • p-8: 设置元素的内边距(padding)为8个单位。这有助于增加元素内部的空间,使其内容不会紧贴边缘。
  7. text-2xl:

    • text-2xl: 设置文本的字体大小为“extra large”。这是一个相对大小,具体大小取决于你的Tailwind配置。
  8. font-bold:

    • font-bold: 设置文本的字体粗细为粗体。这有助于强调或突出显示重要的文本信息。
  9. mt-10:

    • mt-10: 设置元素的上外边距(margin-top)为10个单位。这有助于在元素之间创建垂直空间。
  10. font-semibold:

    • font-semibold: 设置文本的字体粗细为半粗体,介于正常和粗体之间。
  11. underline:

    • underline: 为文本添加下划线,通常用于表示链接或可点击的文本。
  12. underline-offset-4:

    • underline-offset-4: 设置下划线与文本之间的垂直偏移量为4个单位。这可以改变下划线与文本之间的间距,使其更加美观或符合设计要求。
  13. absolute:

    • absolute: 将元素的定位设置为绝对定位。这意味着元素会脱离正常的文档流,并相对于其最近的已定位祖先元素进行定位。如果没有已定位的祖先元素,则相对于初始包含块进行定位。
  14. left-[50%]:

    • left-[50%]: 使用CSS的left属性将元素水平定位到其父容器的中心。这里的50%是相对于父容器的宽度来计算的。注意,这里的方括号表示这是一个任意值,而不是Tailwind的预设值。
  15. top-5:

    • top-5: 设置元素的上边缘与其父容器的上边缘之间的距离为5个单位。当元素使用绝对定位时,这可以帮助你控制元素在垂直方向上的位置。
  16. h-40:

    • h-40: 设置元素的高度为40个单位。这可以确保元素具有一致的高度,无论其内容如何变化。

13. Grid 布局

一般来说,对于卡片组件的排布我们采用的是 grid 布局,如下所示:

<div className="mt-10 grid grid-cols-1 justify-between gap-x-6 gap-y-24 md:grid-cols-2 xl:grid-cols-[repeat(3,25%)]">
    {items.map((item) => (
      <Card key={item.id} item={item} onClick={onClickCard} />
    ))}
</div>

效果如下:

image.png

屏幕较宽的时候:

image.png

代码解释:

  1. mt-10:

    • 含义:margin-top 设置为10个单位。
    • 作用:给这个<div>容器的顶部增加一定的外边距。
  2. grid:

    • 含义:将容器设置为网格布局。
    • 作用:允许子元素在网格内进行布局。
  3. grid-cols-1:

    • 含义:在默认情况下,网格被划分为1列。
    • 作用:设置网格的列数为1,即所有子元素默认情况下会堆叠成一列。
  4. justify-between:

    • 含义:网格中的元素在主轴(这里是水平方向)上均匀分布,首个元素放在开始位置,最后一个元素放在结束位置。
    • 作用:使得网格中的卡片在水平方向上分布均匀,并保持首尾两端对齐。
  5. gap-x-6gap-y-24:

    • 含义:gap-x-6 设置网格中元素之间的水平间隙为6个单位,gap-y-24 设置垂直间隙为24个单位。
    • 作用:控制网格中子元素之间的间距。
  6. md:grid-cols-2:

    • 含义:在中等屏幕大小(md断点)及以上,网格列数变为2。
    • 作用:响应式设计,使得在中等屏幕大小时,网格会自动分为两列。
  7. xl:grid-cols-[repeat(3,25%)]:

    • 含义:在超大屏幕大小(xl断点)及以上,网格被划分为三列,每列占据25%的宽度。
    • 作用:在更大的屏幕上,网格会自动分为等宽的三列,以适应更宽的显示区域。

总结布局规则

  • 默认情况下,所有<Card />组件会堆叠在一列中。
  • 当屏幕宽度达到中等大小时,这些卡片会自动分为两列。
  • 当屏幕宽度达到超大大小时,卡片会自动分为三列,并且每列宽度相等。

每个<Card />组件是网格中的一个子元素,它们会根据屏幕大小的变化而自动调整布局。这样的设计使得布局具有响应性,能够适应不同大小的显示设备。

14. 过渡效果

在这一小节中,我们一起实现一个有过渡的,从右侧出现的 SideBar 效果。

代码如下:

export function Sidebar({ children, isOpen, onClickClose }) {
  return (
    <div>
      <div
        className={`dark:bg-night fixed right-0 top-0 z-50 h-screen w-full transform overflow-y-auto  bg-white p-5 shadow-lg transition duration-300 md:w-[50%] lg:w-[35%] ${
          isOpen ? "translate-x-0" : "translate-x-full"
        }`}
      >
        <button
          onClick={onClickClose}
          className="absolute right-4 top-4 p-2 font-bold text-black dark:text-white"
        >
          X
        </button>
        {children}
      </div>
      {isOpen && (
        <div className="fixed left-0 top-0 z-20 h-full w-full bg-black opacity-50" />
      )}
    </div>
  );
}

下面是每个 Tailwind 类名的含义:

  1. dark:bg-night: 当在暗色模式下时,背景颜色会变为 "night" 色(需要在 Tailwind CSS 配置文件中定义 "night" 这个颜色值)。

  2. fixed: 定位方式为固定(fixed),使元素始终位于视口的同一位置。

  3. right-0: 将元素的右边距设置为 0,使其紧贴父元素的右边。

  4. top-0: 将元素的上边距设置为 0,使其紧贴父元素的顶部。

  5. z-50: 设置元素的堆叠顺序(z-index)为 50,确保它覆盖其他元素。

  6. h-screen: 设置元素的高度为视口的完整高度。

  7. w-full: 设置元素的宽度为父元素的完整宽度。

  8. transform: 允许应用 CSS 变换,如平移、旋转等。

  9. overflow-y-auto: 当内容超出元素高度时,显示垂直滚动条。

  10. bg-white: 设置背景颜色为白色。

  11. p-5: 设置元素的内边距为 1rem(5px,取决于配置文件中的断点值)。

  12. shadow-lg: 应用较大尺寸的阴影效果。

  13. transition: 允许元素的属性值在变化时有过渡效果。

  14. duration-300: 设置过渡效果的持续时间为 300ms。

  15. md:w-[50%]: 在中等尺寸的屏幕上(通常是768px宽),元素的宽度为 50%。

  16. lg:w-[35%]: 在大尺寸的屏幕上(通常是1024px宽),元素的宽度为 35%。

  17. translate-x-0: 当 isOpentrue 时,应用一个 X 轴的平移变换,使元素向左移动 0px,即不移动。

  18. translate-x-full: 当 isOpenfalse 时,应用一个 X 轴的平移变换,使元素向左移动其父元素的完整宽度,从而使它完全离开视口。

  19. absolute: 定位方式为绝对定位,相对于其最近的非 static 定位的祖先元素进行定位。

  20. right-4: 设置按钮的右边距为 1rem。

  21. top-4: 设置按钮的上边距为 1rem。

  22. p-2: 设置按钮的内边距为 0.5rem。

  23. font-bold: 设置字体权重为 "bold"。

  24. text-black: 设置文本颜色为黑色。

  25. dark:text-white: 当在暗色模式下时,文本颜色会变为白色。

  26. fixed left-0 top-0 z-20 h-full w-full bg-black opacity-50: 当 isOpentrue 时,显示一个固定定位的黑色背景层,其 z-index 为 20,高度和宽度都占满整个屏幕,并且透明度为 50%。

至于 sideBar 动画的实现原理,它是通过 CSS 的 transition 属性来完成的。在 Sidebar 组件中,当 isOpen 属性为 true 时,侧边栏会通过 translate-x-0 类应用一个平移变换,使其从屏幕外移动到可视区域内。当 isOpenfalse 时,通过 translate-x-full 类应用平移变换,使其从可视区域内移动到屏幕外。由于设置了 transitionduration-300 类,这个移动过程会有平滑的过渡效果,持续时间为 300 毫秒。这种过渡效果创建了一种动画的感觉,使得侧边栏的打开和关闭看起来更加流畅。

15. tw-merge 的使用

tw-merge 是一个用于合并和处理 Tailwind CSS 类名冲突和优先级的库。本小节结合代码说明使用这个库的方法:

首先,你需要在你的项目根目录下安装 tw-merge。打开终端,输入以下命令来安装:

npm i tw-merge

安装完成后,你需要在你的代码中导入 twMerge 函数。

import { twMerge } from 'tw-merge';

注意,twMerge 是一个函数,你需要使用大括号 {} 将其从 tw-merge 模块中解构出来。

现在你可以使用 twMerge 函数来合并和处理你的 Tailwind CSS 类名了。假设你有一些冲突的类名,比如 px-2(设置左右内边距为 0.5rem)、py-2(设置上下内边距为 0.5rem)和 p-4(设置所有方向的内边距为 1rem),你可以这样使用 twMerge

const classes = twMerge('px-2 py-2 p-4');

twMerge 函数会处理这些类名之间的冲突,并根据 Tailwind CSS 的规则确定哪个类名应该优先。在这个例子中,p-4 会覆盖 px-2py-2,这是因为使用了 tw-merge 强制后面的样式覆盖前面的样式,尽管前面的样式的优先级更高

通过这种方式,tw-merge 帮助你解决了 Tailwind CSS 类名冲突和优先级的问题,让你的代码更加干净、清晰。

在实际的项目中一般会写成如下的形式:

  <select
    onChange={(e) => onChange(e.target.value)}
    value={value || ""}
    className={twMerge(
      `w-24 appearance-none border border-gray-300 bg-white p-4  ${className}`,
    )}
  >

如此一来,后面传入子组件的 className 总是会生效的

16. 设置持久化的主题色

结合 local Storage 我们可以很容易的在 Tailwind CSS 中设置并切换主题色,如下代码所示:

import { BiMoon, BiSun } from "react-icons/bi";

export function App() {
  // ...(其他状态和效果代码保持不变)

  useEffect(() => {
    const isDarkMode = localStorage.getItem("isDarkMode");
    if (isDarkMode === "true") {
      window.document.documentElement.classList.add("dark");
    }
  }, []);

  const toggleDarkMode = () => {
    window.document.documentElement.classList.toggle("dark");
    localStorage.setItem(
      "isDarkMode",
      window.document.documentElement.classList.contains("dark")
    );
  };

  // ...(其他状态和渲染代码保持不变)

  return (
    <div className="animate-fadeIn p-10 dark:bg-night xl:px-24">
      {/* ...(其他组件保持不变) */}
      <div className=" fixed bottom-4 right-4">
        <button
          onClick={toggleDarkMode}
          className="rounded-full bg-night-50 px-4 py-2 text-white shadow-lg dark:bg-white dark:text-night"
        >
          <BiSun className="hidden dark:block" />
          <BiMoon className="dark:hidden" />
        </button>
      </div>
    </div>
  );
}

代码解释:

  1. 在组件挂载后,通过useEffect检查localStorage中是否存储了暗色模式的状态,并据此为document.documentElement添加或移除dark类。

  2. toggleDarkMode函数用于切换暗色模式,它通过切换document.documentElement上的dark类来实现,并更新localStorage中的状态。

  3. 渲染部分包含一个按钮,该按钮绑定了toggleDarkMode函数,用于触发暗色模式的切换,并显示了不同的图标来表示当前是否是暗色模式。按钮的样式也根据暗色模式的状态而改变。