4、React+Tailwind CSS

前言

今天,我将带你们走进一个完全不同的前端开发世界,学习如何用 Tailwind CSS 搭配 React,快速高效地构建现代化网页。

另外,Tailwindcss 的样式可以通过下面的这个在线演示平台进行在线演示:

Tailwindcss 在线演示平台;

其也是 Tailwindcss 的在线文档,可以访问学习。

为什么要学 Tailwind CSS?

作为现代前端开发的必备工具,Tailwind CSS 从根本上简化了开发流程,尤其是在处理样式时。

传统的 CSS 开发,你是不是经常在 HTML 和 CSS 文件之间切换,命名冲突、响应式设计又让你焦头烂额?

别担心,Tailwind CSS 的原子类方法让你完全摆脱这些困扰。

我们会深入探讨它如何在不依赖外部样式文件的情况下,让你在 HTML 中直接编写样式,提升开发效率的同时避免命名冲突。

React + Tailwind CSS 如何完美结合?

当你学会了 Tailwind CSS,你会发现它与 React 是如此的契合。

在这门课程里,我们将一步步教你如何在 Vite 创建的 React 项目中,配置和使用 Tailwind CSS。

通过几个简单的步骤,你就能在项目中快速启用 Tailwind,使用它的类名进行样式编写,简洁、直观,效率翻倍!

Flexbox 和 Grid 布局技巧

布局一直是前端开发中最棘手的问题之一,但在 Tailwind CSS 中,Flexbox 和 Grid 让布局变得简单又强大。

在课程中,我们会教你如何使用 Flexbox 创建灵活的布局,如何利用 Grid 精确控制元素位置,从电商网站的商品卡片到响应式页面布局,Tailwind 的布局系统将是你开发过程中的得力助手。

响应式设计的完美解决方案

想让你的网站在手机、平板、PC 上都表现得完美吗?那就必须学会响应式设计!

Tailwind CSS 提供了强大的断点系统,可以帮助你轻松地实现移动优先的设计。

我们将深入分析如何利用 Tailwind 的内置断点类来实现跨设备的样式覆盖,让你的网页在不同屏幕上自如适配。

让动画为你的页面加分

你是否想过在你的网页上加入一些动效,让页面看起来更有活力?

Tailwind CSS 的动画系统就是为此而生。

我们将教你如何使用内置的 animate-spin、animate-ping 等动画类,甚至可以自定义动画,通过控制关键帧来制作更复杂的动效,给用户带来更流畅的互动体验。

状态交互效果

点击、悬停、聚焦这些小小的交互效果,往往决定了用户体验的好坏。

课程中,我们将深入讲解如何用 Tailwind 的状态变体实现按钮悬停发光、输入框聚焦特效等动态效果。

你还将学到如何利用 React 的 useState 和 Tailwind 的 peer 类来做更细腻的交互设计,让你的界面不止于静态展示,而是和用户产生更多的互动。

暗黑模式的流畅切换

现如今,暗黑模式已经成为许多用户的首选。课程将向你展示如何在 React 和 Tailwind 中实现暗黑模式的流畅切换,从简单的主题切换到自定义的过渡动画,让你的网站在两种模式下都能自如展现,满足用户不同的视觉需求。

实战:产品推荐组件

课程最后,我们将通过一个实际项目------产品推荐组件,帮助你掌握如何在 React 和 Tailwind 中结合布局、响应式设计、交互效果等多个知识点,打造一个完整、功能丰富的组件。通过这个案例,你将能够在真实项目中轻松应对各种挑战。

小结

这不仅仅是一门技术课程,更是一场前端开发的精彩旅程。在这里,你将学会如何结合 React 和 Tailwind CSS,高效、简洁地构建美观、功能强大的网页。

CSS 开发四大酷刑

朋友们,这个场景熟不熟悉?

你刚在 HTML 里写完一个 <div class="header">,准备开开心心去 CSS 里给它加点样式,结果诡异的事情发生了------样式,死!活!不生效!

你以为自己眼花了,于是就开始在 HTML 和 CSS 文件之间反复横跳。

先检查一下 HTML 类名拼写对不对;

然后切回 CSS 检查选择器优先级是不是被谁干掉了;

接着打开浏览器控制台,看有没有报错;

实在不行,就偷偷加个 !important 想强行"扳回来";

但到最后发现......你只是拼错了一个字母!!就这么个低级错误,活生生查了半小时,键盘都快砸了!

这个开发过程,简直像是在玩三个魔方:眼睛盯着 HTML 结构不敢眨,脑子里还得过 CSS 的逻辑关卡,手里不停地调试浏览器效果------精神多线程都快烧掉了!

痛点1:文件跳转眩晕症

就像我们开头说的,改个样式得在 HTML 和 CSS 文件之间来回横跳。你刚在 HTML 改完类名,下一秒就 Ctrl+Tab 去找 CSS,找到了还得定位、理解、判断是不是你要改的那段。

特别是项目大了之后,一个组件可能分了七八个文件夹,每次定位样式都像是在做"文本迷宫"大冒险。久而久之,你会发现自己开发的时候眼神空洞、手指机械,切换窗口的频率比写代码还高。你不是在写 CSS,你是在打切屏大战!

痛点2:样式复读机综合症

你有没有写过这种样式:

typescript 复制代码
.card-padding {
  padding: 1rem;
}

.modal-padding {
  padding: 1rem;
}

.alert-padding {
  padding: 1rem;
}

明明就是一模一样的 padding,却为了不同的上下文、不同的组件,硬生生起了一坨又一坨冗余的类名。这就是程序员"命名焦虑症",感觉每一个类名都必须有意义、得贴合场景、还得防止重复。

结果就是,样式写得越来越多,维护难度越来越大,你的 CSS 文件就像一个"复读机工厂",类名越来越长,功能却都差不多。

痛点3:响应开发微积分

移动端适配,是每个前端开发永远的噩梦!你要支持 PC,还得兼容平板、手机,结果就是一大堆 @media 像数学题一样堆满整个 CSS 文件,每次写媒体查询都要计算断点、调整布局(展示复杂CSS代码),比解微积分还烧脑!

typescript 复制代码
@media (min-width: 768px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 1024px) {
  .grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

每写一条都要算半天------断点用多少?列数怎么调?哪个设备需要展示几列?

这种开发体验真的太像解微积分了,脑子一点都不能飘。你以为在写页面,其实你在参加信息学奥赛。

痛点4:性能焦虑晚期

为了赶进度,你引了一个 UI 框架,结果打包出来的 CSS 文件------200KB。

200KB 是什么概念?用户点开你的网站,得等加载条慢悠悠地走完;你的核心样式只用了 5%,却得拖着 95% 的"累赘"一起上线。

有时候你甚至不敢用太多样式,怕影响首屏加载速度。你不是在写好看的界面,而是在跟"性能天花板"硬碰硬。

前端的悲哀是什么?是我们拼了命写样式,却被一坨不需要的 CSS 拖了后腿。

Tailwind 破局五连击

现在,这些痛点今天一次性解决!轮到 Tailwind 上场了。

我们来一波连击,看它是怎么一拳拳打爆这些痛点的!

🥊 第一击:告别文件跳转眩晕症

在 HTML 和 CSS 文件之间疯狂横跳,写完结构再去翻 CSS,效果不对又回来改结构,这完全就是在跟自己过不去!

Tailwind 怎么破?结构和样式写在一起,一眼看到全貌! Tailwind 的解法简单粗暴:

typescript 复制代码
<header class="sticky top-0 bg-white shadow-md z-50">
  <!-- 所有样式一目了然 -->
  <!-- 定位?阴影?层级?直接写脸上! -->
</header>

现在你的 HTML 自己就是样式说明书!就这一个标签,结构、布局、样式全搞定。不需要切文件、不用查文档、不必起类名------三倍效率直接拉满!这感觉就像从绿皮火车换到磁悬浮,回头看看过去的开发方式...妥妥的史前文明!

🥊 第二击:样式复读机终结者

以前我们为了复用样式,只能复制粘贴或者起各种奇奇怪怪的类名:

typescript 复制代码
.card-padding {
  padding: 1rem;
}

.modal-padding {
  padding: 1rem;
}

写得像复制机,还特别容易忘。

但在 Tailwind,你不需要给样式命名了:

typescript 复制代码
<div class="p-4">卡片</div>

<div class="p-4">弹窗</div>

就用现成的类 p-4,哪里用就加哪里,没有命名焦虑,也没有复制粘贴,省心又直观。

🥊 第三击:响应式写法压感轻如羽

写媒体查询是不是让你头秃?想适配不同设备,要写一堆冗长的 CSS:

typescript 复制代码
@media (min-width: 768px) {
  .card {
    padding: 2rem;
  }
}

但在 Tailwind 里,响应式写法简直像写"自然语言"一样直白:

typescript 复制代码
<div class="p-4 md:p-8">内容</div>

md:p-8 的意思就是在中等屏幕以上,padding 是 8,其余时候就是 4。无需写一行 CSS,响应式直接融合进类名里,精简又直观。

🥊 第四击:CSS 体积控制狂魔

传统 CSS 经常不知不觉越写越多,一个项目几十 KB 起步,动不动还引 UI 框架就炸了,加载慢得能让人怀疑人生。

Tailwind 的解法是------构建时自动"裁剪"没用到的类名!

只保留你实际在 HTML、JSX 里用到的类,配合 purge 或 content 设置,最终打包出来的 CSS 文件轻得像羽毛。

typescript 复制代码
// tailwind.config.js

content: [
  "./index.html",
  "./src/**/*.{js,ts,jsx,tsx}",
],

你用多少,打包就保留多少,小而美,精而快,性能飞升!

Tailwind 的 vite 开发配置

写前端,配置环境总是像搬砖盖房,从装门到贴瓷砖,一堆麻烦事?

不用担心,今天,我手把手教你,利用 Vite 这个脚手架工具,搭配 React,从零开始配置 Tailwind CSS 。一步一步,让你的项目飞起来。

安装依赖 Install Tailwind CSS

我们第一步,先把 Tailwind CSS 安装进项目里。命令很简单:

typescript 复制代码
npm install tailwindcss @tailwindcss/vite

需要注意的是,我们同时安装了两个依赖,第一个是Tailwind CSS 这个样式框架的核心库 ;第二个 @tailwindcss/vite 是它在 Vite 里的专用插件。

这个@tailwindcss/vite是一个 专为 Vite 优化的插件,

主要作用是:

把 Tailwind 的编译流程和 Vite 无缝对接起来。

并且自动帮你处理开发过程中的热更新、构建优化等事情;

这个插件就是给两者牵线搭桥的中间人。

配置 Vite,让系统听得懂 Tailwind

接下来,咱们要做一件事:让 Vite 听懂 Tailwind 的语言。

你可以把 Vite 想象成项目的大管家,所有的插件、资源加载、打包优化,都是它在安排。但 Tailwind 是一个"外来嘉宾",它讲的是另一套样式语言。如果你不提前介绍一下,它俩压根搭不上话。

所以,我们得在 vite.config.js 里写上一段话,让两位先认识一下。

typescript 复制代码
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [react(), tailwindcss()],
  ...
}

这段配置的核心就是:tailwindcss()。它的意思是:"喂,Vite,我打算用 Tailwind 写样式了,你记得帮我打包、热更新、处理样式文件哈。"

如果你没加上这行代码,Tailwind 的语法你可能是写上了,但 Vite 直接就装作没看见------开发环境里没样式,生产环境里也白板一片,这就好比你明明请了个厨子进厨房,但厨房根本没开火,他再会做菜也没用。

所以,这一步特别关键:

Vite 是引擎,Tailwind 是燃料,得接上线才能跑起来!

引入 Tailwind 样式

好,现在到了关键一步:接通 Tailwind 的样式。我们要在项目的 CSS 文件里------通常是 src/index.css 或者 src/main.css 这样的入口文件------加入下面这一行:

typescript 复制代码
@import "tailwindcss";

这句话的意思特别直白,就是:"把 Tailwind 的所有基础样式全都引进来!"这一步,虽然代码只有一行,但作用却是全局级别的关键。

顺带说一句:不要忘记确认这个 CSS 文件有没有在 main.jsx 或 index.jsx 中被引入,否则即使你写了这句 @import,浏览器也压根不会加载它,那就等于水管接好了但总闸根本没开------还是没水出。

写个测试组件试试看

走到这一步,我们已经完成了 Tailwind 的安装、插件配置、样式引入,基本上该铺的路都铺好了。

那接下来,就该验证一下我们的 Tailwind 工具箱,是不是已经准备就绪、可以上工了。

咱们在 main.jsx 文件里,写一个小小的测试页面,代码很简单:

typescript 复制代码
function App() {
  return (
    <h1 className="text-3xl font-bold text-blue-600">React + Tailwindcss</h1>
}

好,重点来了------你看到这三个类名了吗?

text-3xl:代表这段文字的字号是 3 倍大,超显眼的标题效果;

font-bold:让字变粗,强调视觉权重;

text-blue-600:把文字染成一个清爽不俗的蓝色,视觉观感非常舒服。

你可以理解为:我们用一行代码,就搞定了字号、粗细和颜色三个样式维度,而且完全不用翻 CSS 文件、起类名、调样式。

写完这段代码之后,记得运行项目:npm run dev

然后打开浏览器,你的页面上应该出现一行非常有存在感的大字标题:"Tailwind CSS + Vite + React"------蓝色加粗,醒目又整洁。

如果你能看到这个效果,恭喜你!🎉

这说明:你的 Tailwind 配置已经完全生效了,原子类已经 ready for battle,接下来你可以随心所欲地写页面、搭组件、做响应式了。

Flex三板斧助你吊打传统 CSS

朋友们,今天我们要解锁 Tailwind 的两大杀器:Flexbox 和 Grid!有人说它们是 前端界的倚天剑与屠龙刀,用好了,能纵横前端江湖,从弹性卡片到响应式宫格,全都不在话下!

今天我们聚焦第一把神器 ------ Flexbox!想要布局走位风骚,页面结构灵活百变,Flex 是你永远的好搭档!

Flexbox 核心三板斧:掌握了就是开挂!

这三招就像是 Flex 布局的传世武功,堪比前端游戏里的隐藏秘籍,学会之后,你就不再被"居中对齐怎么搞"、"卡片怎么自适应高度"、"侧边栏怎么固定"这些问题反复折磨。

别人在调 margin 和 padding,你轻轻一个 flex-1 就解决战斗;别人写一堆 CSS 还不如你一句 flex 写得优雅干净。

可以说,掌握这三板斧,就像给布局系统装上外挂------不光写得快,还写得稳,样式简洁、响应灵活、代码量直线下降,开发效率直接起飞!

三板斧如下:

其中 flex-none 有几个常用的场景:

固定头像、按钮:在列表或者卡片里保持一致尺寸;

侧边栏导航:页面左侧导航栏保持定宽不变形;

电商商品卡片:图像部分设置为 flex-none,文字区域用 flex-1 自适应。

直接甩出来可能有点抽象,我们将在下一节通过实操来进行学习。

Flex 终极对齐奥义

什么是 Flex 对齐?为什么你一定要会?

在页面布局中,"对齐" 是门玄学。左对齐、居中、垂直靠底......以前写个页面,得翻箱倒柜找 margin、padding、position、text-align、vertical-align,还常常调到你自闭。

但现在,Tailwind 给你塞了一颗定心丸,只要一句:

直接水平 + 垂直双居中,像武侠小说里那种一出手就是降龙十八掌的感觉,干净利落、全场通吃。

所以你可以把 Flex 对齐 理解为:

网页元素的定位神功,Tailwind 是你修炼的武功秘籍。

谁学会了,谁排版不再崩溃。

那它到底是怎么回事?

Flex 是 Flexbox 的简写,想学 Flex 对齐,得先懂"坐标"。

  • x轴(横向)使用 justify-*,控制元素左右排布,默认是从左到右
  • y轴(纵向)使用 items-*,控制元素上下对齐,默认是垂直方向

Flex 对齐密码

记住这个 Flex 对齐密码:flex items-center justify-between,翻译成人类语言是:『垂直居中 + 水平两端对齐』,电商导航栏终极方案。

typescript 复制代码
<nav class ="flex items-center justify-between px-4 h-16 bg-white shadow-md">
  <a href="#" class="text-xl font-bold">
    LOGO
    </a>

    <div class="flex gap-6">
      <a href="#">
    首页
    </a>
    <a href="#">
    商品
    </a>
    <a href="#">
    购物车
    </a>
    </div>
    </nav>
  • items-center → 交叉轴对齐(垂直方向)
  • justify-between → 主轴对齐(水平方向)
  • gap-6 → 子项间距控制(替代 margin 的优雅方案)

justify-* 横向对齐详解

在 Flex 世界里,justify-* 就是专门负责 X 轴(横向)对齐的指挥官。

  • justify-start 全部元素靠左

比如说,当你使用justify-start,代表全部元素靠左站,就像小学生排队,第一个人站在左边,后面依次往右排。很适合用在商品列表、表格、普通布局中。

typescript 复制代码
<div class ="flex justify-start h-32 bg-gray-100 p-4">
    <div class="h-16 w-16 bg-blue-500">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-16 w-16 bg-red-500">C</div>
</div>
  • justify-center全体归中!

如果我们换成 justify-center:

typescript 复制代码
<div class ="flex justify-center h-32 bg-gray-100 p-4">
    <div class="h-16 w-16 bg-blue-500">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-16 w-16 bg-red-500">C</div>
</div>

三个盒子"扎堆"居中站,左边和右边都留有相同的空白,看起来超规整!常用于按钮组、提示信息、居中的标题啥的。

  • justify-end 靠右站好

想让所有元素向右靠,使用 justify-end

typescript 复制代码
<div class ="flex justify-end h-32 bg-gray-100 p-4">
    <div class="h-16 w-16 bg-blue-500">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-16 w-16 bg-red-500">C</div>
</div>

现在,所有盒子都跑到右边去了,左边大片留白,适合放一些靠右的操作,比如"下一步"、"结账"等按钮。

  • justify-between 两边贴边,中间拉满

接下来,还有一种布局叫做,justify-between:

typescript 复制代码
<div class ="flex justify-between h-32 bg-gray-100 p-4">
    <div class="h-16 w-16 bg-blue-500">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-16 w-16 bg-red-500">C</div>
</div>

这样的布局整齐划一,是强迫症最爱。

  • 最后,还有两种布局非常相似,justify-evenly 与 justify-around

justify-evenly:表示所有元素的间距都完全一样,包括元素之间、两边留白。就好像拍集体照,摄影师说:"全部人和墙、和彼此之间距离都要一模一样!"

typescript 复制代码
<div class ="flex justify-evenly h-32 bg-gray-100 p-4">
    <div class="h-16 w-16 bg-blue-500">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-16 w-16 bg-red-500">C</div>
</div>

而 justify-around 就好像摄影师说:"你们平均站好,但我要中间多留点空间看起来舒服点。"

typescript 复制代码
<div class ="flex justify-around h-32 bg-gray-100 p-4">
    <div class="h-16 w-16 bg-blue-500">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-16 w-16 bg-red-500">C</div>
</div>

中间间距更大,左右边距略小。

实际选择建议:

小总结:排队靠它,布局无忧!

Flex 的 justify-*,就像你在喊口号:"向左看齐!"、"居中对齐!"、"平均分布!"

Tailwind 把这些动作都变成了一个类名,让你像在切换滤镜一样切换布局风格------写得快,看得爽,改得顺手!下面是各种justify的总结:

items-* 垂直对齐详解

除了横向上的justify,处理纵向的对齐,请使用items-。

常用对齐方式列表

下面是常用的对齐方式总结:

  • items-start,items-center与items-end的用法与 justify 几乎一样。

使用 items-start 所有元素贴顶站好,常用于聊天气泡、价格标签,所有元素都从顶部开始出现。

typescript 复制代码
<div class ="flex items-start h-32 bg-gray-100 p-4">
    <div class="h-8 w-16 bg-blue-500">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-24 w-16 bg-red-500">C</div>
</div>

纵向居中排列使用items-center,视觉舒服,适合按钮图标配文字、或者元素高度不一致但想齐中线排列的场景。

typescript 复制代码
<div class ="flex items-center h-32 bg-gray-100 p-4">
    <div class="h-8 w-16 bg-blue-500">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-24 w-16 bg-red-500">C</div>
</div>

最后,items-end 则代表让所有元素蹲下排队,比如时间线 UI,所有时间点从底部对齐。

typescript 复制代码
<div class ="flex items-end h-32 bg-gray-100 p-4">
    <div class="h-8 w-16 bg-blue-500">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-24 w-16 bg-red-500">C</div>
</div>
  • 而items-baseline --- 和文字有关,所有元素文字底线对齐
typescript 复制代码
<div class ="flex items-baseline h-32 bg-gray-100 p-4 text-white text-2xl">
    <div class="h-16 w-16 bg-blue-500">A</div>
    <div class="h-24 w-16 bg-green-500 text-4xl">B</div>
    <div class="h-20 w-16 bg-red-500 text-sm">C</div>
</div>

比如,在上面例子中,色块大小不一样,字体大小也不一样,但是ABC三个字却始终处于同一个水平线上,也就是文字的底线对齐。

  • 最后,还有一个items-stretch,他是flex布局的默认行为,将会自动填满容器高度
typescript 复制代码
<div class ="flex items-stretch h-32 bg-gray-100 p-4">
    <div class="w-16 bg-blue-500">A</div>
    <div class="w-16 bg-green-500">B</div>
    <div class="w-16 bg-red-500">C</div>
</div>

子项自己想对齐?用 self-*

前面咱们讲的 justify 和 items-* 都是"家长式管理",父元素统一管理所有孩子的垂直站位。

但有时候,有时候,孩子也会有自己的主见。这时候就可以给某个子项加上 self-*,它会无视老爹的 items-* 安排,自己站队!

  • 第一种情况,使用 self-auto,这是默认值,代表啥都不做,老爹说啥我做啥
typescript 复制代码
<div class ="flex items-center h-32 bg-gray-100 p-4">
    <div class="h-8 w-16 bg-blue-500 self-auto">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-24 w-16 bg-red-500">C</div>
</div>

这就和前面介绍的items-center 效果完全一样。也就是 self-auto 会跟随父级设置,表现和不加 self-* 是一样的。

  • 接下来,再比如self-start 表示 "我就是要贴顶部!"
typescript 复制代码
<div class ="flex items-center h-32 bg-gray-100 p-4">
    <div class="h-8 w-16 bg-blue-500 self-start">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-24 w-16 bg-red-500">C</div>
</div>
  • 最后,self-end 表示 "我就喜欢蹲着"
typescript 复制代码
<div class ="flex items-center h-32 bg-gray-100 p-4">
    <div class="h-8 w-16 bg-blue-500 self-end">A</div>
    <div class="h-16 w-16 bg-green-500">B</div>
    <div class="h-24 w-16 bg-red-500">C</div>
</div>

间隙管理神器:gap

最后,我们来讲讲弹性布局中元素之间的间距是如何管理的。

如果我们想让几个盒子之间有间距,得这么写,使用 margin,边写还得边算,最后一个别加 margin-right,不然组件超出、对不齐,烦得要命!

现在,通过间隙管理神器直接一句:gap-4 就够了,所有兄弟之间自动间距!干净利落,养眼又好维护。

gap 是什么?

gap 是 Flex 和 Grid 布局中的官方间距管理器,专门用来控制子元素之间的间隔(不是外边距,不影响容器边缘)。你可以理解成:

"我们兄弟之间要保持距离,但咱爸妈就别插手了。"

常见用法

下面是gap常见的用法:

如果你只写 gap-4,相当于横竖间距都设置为 1rem(4 × 0.25rem)。

gap VS margin 区别在哪?

所以日常开发推荐先用 gap 解决主干布局,再用 margin 微调个别视觉偏差。

实战对齐组合

比如说,我们可以结合items-center + justify-between + gap 的形式来完成一个上下对齐 + 两端排布的导航栏布局:

typescript 复制代码
<nav class ="flex items-center justify-between px-6 h-16 bg-white shadow">
  <div class="text-xl font-bold">
    LOGO
    </div>

    <div class="flex gap-6">
      <a href="#">
    首页
    </a>
    <a href="#">
    商品
    </a>
    <a href="#">
    我的
    </a>
    </div>
 </nav>

justify-between:左一个 LOGO,右边链接组,两边贴边,中间留白。

items-center:垂直方向对齐在中间(比如图标 + 文本混排)。

Grid 网格布局秘籍

你有没有那种排东西排得焦头烂额的时候?比如做个商品展示区,左一张右一张排不齐,要么歪要么挤,感觉手上像拿着一堆拼图却没个拼图盘。

别急,今天我们召唤神器------CSS Grid,让排版这事儿变得像拼乐高一样简单、清晰、有秩序!

网格召唤术:基础布局从这里开始

来,逐个看一下这几个 Tailwind 的魔法指令:

typescript 复制代码
<div class ="grid grid-cols-3 gap-4">
    <div class=" bg-blue-500 ">A</div>
    <div class=" bg-green-500">B</div>
    <div class=" bg-red-500">C</div>
</div>

grid → 相当于给这个盒子贴上了一张隐形的方格纸,效果就等同于 display: grid。

也就是说,它现在是一个网格容器啦,里面的元素都会自动按网格排列。原来乱七八糟的排布,现在有了章法。

grid-cols-3 → 这句就像是在告诉浏览器:"嘿,来,把这张网格纸分成 3 列!"每列默认是等宽的,就像切蛋糕切成三份,每份都一样大,不偏不倚。

gap-4 → 网格之间要不要留点空隙?当然要啦,这个 gap-4 就是网格之间的呼吸间隔,横向纵向都有,默认为 1rem,让页面看起来更有层次、不会"闷"在一起。

网格定位术:跨行跨列,自如控制格子大小

很多时候,一个格子太小,放不下内容怎么办?来,给它升级一下等级,直接跨行、跨列走起!

typescript 复制代码
<div class ="grid grid-cols-3 gap-4">
    <div class="col-span-2 bg-blue-500 ">占两列</div>
    <div class="row-span-2 bg-green-500">占两行</div>
    <div class="col-start-2 bg-red-500">从第二列开始</div>
</div>

关键词:

col-span-* / row-span-* → 控制一个格子要横跨几列 / 几行,比如 col-span-2 就是占两个格子的位置,视觉上就大了一圈。

col-start-* / row-start-* → 这个很有用,表示这个元素从第几列/行开始摆放,特别适合定点对齐。

所以你可以随心所欲地把某个重要内容横跨两列,把某张图从第三行开始展示,布局变灵活了,排版不再死板!

精准控制术:把网格玩出花样

当你已经会基本的跨行跨列,我们可以开始玩一些"定位精度打磨"的高阶操作:

typescript 复制代码
<div class ="grid grid-cols-6 gap-4">
    <div class="col-span-4 col-start-2 bg-blue-500 ">01</div>
    <div class="col-start-1 col-end-3 bg-green-500">02</div>
    <div class="col-span-2  col-end-7 bg-red-500">03</div>
    <div class="col-start-1  col-end-7 bg-amber-500">04</div>
</div>

关键词:

col-start-* / row-start-* → 指定起点,让你精确决定元素在哪开始"出生"。

col-end-* / row-end-* → 指定终点,也就是元素在第几列/行结束。这个跟 span 的方式不同,更像是你直接标了"从几开始到几结束"。

place-content-center / place-items-end 等 → 控制整个网格区域内的元素怎么对齐(水平 + 垂直的组合拳),比如整体居中、整体底部对齐等。

这个阶段,你就不再只是"用 Grid",而是真正开始驾驭 Grid,像在下棋一样,把每一个元素精确摆放到你想要的位置。

高级玩法:当布局不再只是"平均分"

学到这里,你已经能轻松画出等列等行的网格了。是不是有点成就感?但现实中,页面布局往往比这复杂,比如左边放个导航栏,中间是正文,右边还有广告条......平均分?早就不够用了!

这时候,就要上自定义网格布局这张牌,看看下面这个例子,有时候你不想每列都平均分,比如左边要放个导航栏,宽度固定,中间内容要自适应,右边宽度也得固定。这时候就用:

typescript 复制代码
<div class="grid
  grid-cols-[200px_1fr_300px]  <!-- 自定义列宽 -->
  grid-rows-[auto_1fr]          <!-- 定义行高 -->
  gap-4
">
  <header class="col-span-3 bg-blue-500">头部</header>
  <aside class="row-span-2 bg-red-500">侧边栏</aside>
  <main class="col-span-2 bg-green-500">主内容</main>
</div>

解释一下第一行的这个grid-cols-200px_1fr_300px

  • 第1列:200px → 固定宽度,像一块砖,搬也搬不动
  • 第2列:1fr → 弹性单位,自动填充剩余空间,谁灵活谁拿多
  • 第3列:300px → 也是固定宽度,适合放广告、工具栏之类的内容

Tip:这个 fr 单位是 Grid 的黑科技,代表"份数"------你可以理解成"抢地盘"的单位。1fr 表示我想要一份剩下的空间,多个 fr 会按比例来分配哦!

列能定宽,行也可以定高。第二行的 grid-rows-auto_1fr 就是用来设置行高的:

  • 第1行:auto → 高度由内容撑开,谁内容多谁撑得高,适合 header 这种不固定高的元素。
  • 第2行:1fr → 占据剩下的高度,适合内容区,比如正文或主要内容展示区。

最后的这个gap-4可以设置网格中横向和纵向的间距,让每一块区域都不"贴脸",页面看起来更舒服、有呼吸感。这种"上内容撑开、下自动填满"的套路,是很多后台布局或仪表板的标配。

总结一下这波高级操作

这段布局虽然短短几行代码,但里面已经包含了 Grid 的精华------自定义列宽 + 灵活行高 + 跨行跨列 + 网格间距,基本上你想排成什么样子,它都能配合你。

换句话说,这种写法适合那些:

  • 页面不对称(如侧边栏 + 内容区 + 工具栏)
  • 有复杂头部或底部结构
  • 想做后台管理页面、仪表盘、三栏电商页面的你

Flex vs Grid 终极抉择

"Flex 还是 Grid?我到底该用谁?"

是不是每次写布局你脑袋都要开个会:左边 Flex 嚷着"我能自动换行!";右边 Grid 说"我能精准定位!"------谁都不想错过!

别急,咱来一张对比速查表,对比一下flex和grid的不同适用场景:

一句话总结:

  • Flex 是"线性布局高手",适合处理一排排元素
  • Grid 是"棋盘型大佬",擅长管理复杂区域结构
  • Flex + Grid:梦幻联动操作

有时候,你真的不需要二选一------因为它们可以合体!比如,这个例子中:

typescript 复制代码
<div class="grid md:grid-cols-2 gap-4">
  <div class="flex flex-col p-4 bg-gray-100 rounded-lg shadow">
    <div class="flex-1">内容区域,自动填充</div>
    <div class="flex-none mt-2">底部按钮</div>
  </div>
</div>
  • 外层用 Grid 把整体区域划好格子;
  • 每个格子里再用 Flex 实现内部结构的弹性对齐。

就像一个小区的格局由 Grid 决定,而每家每户怎么摆沙发、电视、书桌,交给 Flex 自由安排。

这就是布局届的"变形金刚":合体战斗,天下无敌!

避坑指南

种布局方式都不是完美的,都有些小坑,咱得提早绕开。

Flex 常见坑:子项溢出

typescript 复制代码
<!-- ❌ 容器宽度不够,子元素硬撑撑爆布局 -->
<div class="flex bg-amber-600">
  <div class="w-64 bg-blue-500">卡片A</div>
  <div class="w-64 bg-red-500">卡片B</div>
</div>

缩小屏幕,随着容器宽度越来越小,固定宽度的子元素卡片A和卡片B会硬撑撑爆整个布局,导致组件益处页面。

这个时候,我们可以加个 flex-wrap 就能让它自动换行!

typescript 复制代码
<!-- ❌ 容器宽度不够,子元素硬撑撑爆布局 -->
<div class="flex flex-wrap bg-amber-600">
  <div class="w-64 bg-blue-500">卡片A</div>
  <div class="w-64 bg-red-500">卡片B</div>
</div>

Grid 常见坑:列数不够用?

比如说,我们在设置网格的时候,只设置了3列,但是在子元素中却设置了col-span 为 4,导致子元素太宽,超过了 grid-cols-3 的限制,我们的布局就崩溃了。

typescript 复制代码
<!-- ❌ col-span-4 超过了 grid-cols-3 的限制,布局会崩! -->
<div class="grid grid-cols-3 bg-blue-500">
  <div class="col-span-4 bg-red-500">Oops,我太宽了</div>
</div>

这个坑叫做:"隐式网格失控"。Grid 有时候太听话了,直接照你写的来,结果一不小心列数不够,就乱套了。

其实正确写法应该用自适应列处理, auto-cols-minmax(0,1fr) 这样 Grid 就能根据空间"自动造列",再也不用担心"列不够"了!

typescript 复制代码
<div class="grid auto-cols-[minmax(0,1fr)] bg-blue-500">
  <div class="col-span-4 bg-red-500">没事,我能自适应</div>
</div>

总结

  • Flex 是单维度流动的艺术 → 水平 or 垂直一字排开最拿手;
  • Grid 是二维空间的绝对掌控 → 精准网格、区域控制它说了算;
  • 两者联手,天下我有 → 外层排格子,内层排细节,完美配合;

Tailwind CSS 就像积木工具箱,让你用语义化 class 组合出任何布局结构,零 CSS 也能排出大师级界面。

手机、平板、PC:三屏联动响应式

响应式开发,这是一个让前端人又爱又恨的词!

爱的是它能适配万千设备、各种奇葩屏幕尺寸,让你的网站在手机上也美得不像话;

恨的是写起来跟解微积分一样复杂------媒体查询、断点换算、像素对齐,一顿操作猛如虎,效果却像素对不住。

但现在好了!我们有了 Tailwind 的响应式变形术,一切变得优雅丝滑,只需加前缀,页面自动识屏换装,像变形金刚一样灵活机动!

核心原理拆解:Tailwind 的"断点联盟"

Tailwind 内置了一套响应式断点系统,这些断点就像是网页的"换装信号灯",尺寸一到,就触发换装:

注意:这些断点不是瞎定的!

Tailwind 团队可是拿数据跑过统计分析的,这套分段覆盖了超过 90% 的设备分辨率,可以说是 CSS 世界的黄金分割点。你站在这些断点上写样式,妥妥的"站在巨人的肩膀上"。

核心原则:

  • 移动优先:所有不加前缀的类,默认就是为小屏设备服务的。你写的第一个样式,先给手机看!
  • 断点逻辑:像 md:text-center 这样,意思很简单:屏幕宽度 ≥768px 的时候才让文字居中,其余设备不影响。
  • 自定义扩展:如果内置的断点不够用?可以直接在 tailwind.config.js 里拓展,比如加个 "3xl": "1600px" 来满足你那台特大 4K 生产力神器。

移动优先奥义:先手机,后电脑!

想象一下你在给网页穿衣服,不能一上来就套羽绒服对吧?得先穿内层(手机样式),再一层层叠加(大屏覆盖)。

看例子秒懂:这段代码实现了一个响应式布局,子元素在手机端纵向堆叠,到了大屏电脑端则变为左右并排的双栏布局。

typescript 复制代码
<div class ="flex flex-col lg:flex-row">
    <div class="w-full lg:w-1/2 bg-blue-500 ">item1</div>
    <div class="w-full lg:w-1/2 bg-green-500 ">item2</div>
</div>
  • 默认状态(也就是手机端)是 flex-col → 垂直排列
  • lg: 一出现,就变成 flex-row → 水平并排,适配大屏空间

重点来了:为什么我们不用 sm: 呢?

因为!移动优先的核心哲学是:手机就是起点,所有不加前缀的样式都自动落在手机端,也就是说,只要不加前缀,默认的基础样式都是sm!

这就像你起床穿衣服的顺序 ------ **内衣 = 基础样式,外套 = 大屏样式,**如果你先给大屏写样式,再没加基础样式,那小屏就裸奔了 🤦‍♂️(用户体验直接扑街)。

断点覆盖黑科技:套娃式生长布局

现在我们来解锁一点"断点叠加"的进阶玩法 :想象你的样式是个"会变身的套娃",每个断点,就像为这个娃娃加一层新皮肤,越往后,层级越高。

看这个例子,这段文字会随着屏幕一点点拉大,逐级变大。视觉上非常舒服,用户体验也稳稳在线。这种写法特别适合文章正文、产品标题、Hero 区块等,让用户一进来就感受到页面"在呼吸"、"在自适应"。

typescript 复制代码
<div class="text-sm md:text-base lg:text-lg xl:text-xl">
	这段文字会呼吸
</div>

它的工作原理就像套娃:

  • text-sm:默认字体,小屏专属,先把底打好
  • md:text-base:屏幕 ≥768px,稍微长大点
  • lg:text-lg:到了笔记本或桌面,字体再大一级
  • xl:text-xl:大屏环境,字体最大最醒目!

这就是层层覆盖、渐进增强的神奇力量!

而且 Tailwind 按照从小到大的顺序排列断点,越后写的,优先级越高,会覆盖前面那些断点对应的样式。

注意顺序很关键! 如果你把 xl: 写前面、md: 写后面,结果就很可能乱套,因为"后面的断点会吃掉前面的"。

小结

  • 响应式开发的核心不是写一堆断点,而是合理规划变形点;
  • Tailwind 把响应式写法变成了"写类名"的过程,让你只动手指,不动脑筋;
  • 移动优先+断点覆盖 = 响应式布局的双剑合璧;
  • 记住顺序,别乱套娃,不然样式就打架啦!

状态特效:按钮悬停发光与输入框聚焦

前端交互的最高境界是什么?

是酷炫的动画?还是灵活的布局?不不不,都不是,而是预判用户的操作!当用户还没开口,你的页面就已经做出反应,这才是高手操作!

而 Tailwind 的"状态变体"功能,简直就像给元素装上了感应器,它能读懂用户的每一个小动作,让你成为交互界的 X 教授

今天我们就来实战一下,给页面的按钮、卡片、菜单......装上"情绪感应器",让它们"看你一眼就知道你想点哪儿"。

核心原理揭秘:状态变体就是 CSS 的读心术!

Tailwind 的状态变体,本质上就是在不同用户操作状态下,动态添加对应的样式。

状态语法结构是这样的:

typescript 复制代码
状态名:样式类名

就像是给元素戴上不同的"表情包",举几个例子:

hover: → 鼠标移上去,就好像在说:"诶~别走啊~"

focus: → 聚焦(比如输入框聚焦),就好像在说:"我准备好了,来吧!"

active: → 就是点击瞬间的反应

dark: → 暗黑模式下启用的样式

看下面这颗按钮怎么"读懂你":

typescript 复制代码
<button class="
 transform
 transition-all
 hover:scale-105      <!-- 鼠标移上来,按钮微微放大,来点小调皮  -->
focus:ring-2         <!-- Tab 键聚焦,立刻冒出光环:我准备好了  -->
active:opacity-80    <!-- 鼠标按住的那一刻,变得稍微透明:我感受到了你的点击  -->
disabled:opacity-50   <!-- 如果按钮是禁用状态,看起来就没啥存在感  -->
">
点我有惊喜 👍
</button>

这颗按钮就是个社牛!你碰它一下,它立刻做出反应,让人觉得不点它都有点对不起它!

实战:添加搜索框

好,实战时间。请同学们打开项目,这节课的实战就是给我们的顶部导航栏添加一个有灵魂的搜索输入框。

添加搜索按钮

在Header组件中,我们先在导航栏里添加一个搜索按钮。

1、把按钮们用一个 div 包裹起来,然后加个 gap-2,按钮之间就有自然间距啦:

typescript 复制代码
<div className="flex gap-2">
  <button className="md:hidden" onClick={() => setIsOpen(true)}>
    <AiOutlineMenu size={24} />
  </button>
</div>

2、引入搜索图标

接着,我们从react-icons/ai中引入 AiOutlineSearch 图标:

typescript 复制代码
import { AiOutlineMenu, AiOuelineSearch } from "react-icons/ai";

然后把它放在导航栏右边。注意:搜索按钮不需要像菜单按钮一样隐藏在小屏幕上,它需要全状态显示!

typescript 复制代码
<div className="flex gap-2">
  <button>
    <AiOutlineSearch size={24} />
  </button>
  <button className="md:hidden" onClick={() => setIsOpen(true)}>
    <AiOutlineMenu size={24} />
  </button>
</div>

现在,我们的导航栏就有了菜单和搜索两个核心操作入口。

弹出搜索框

现在按钮有了,接下来就是灵魂注入------点击搜索图标,弹出输入框。还是上节课同样的配方,我们需要使用react的内置函数useState来控制显示隐藏,参数传入false作为默认值,我们默认输入框为关闭的状态。不懂useState的同学不用担心,我们会在下一章详细讲解。

useState的返回是一个数组:

  • 数组的第一项命名为isSearchVisible,这是一个布尔类型的变量,我们用它的状态来控制搜索框的出现和消失:
  • 第二项命名为setIsSearchVisible,这是isSearchVisible状态变化的函数。
typescript 复制代码
const [isSearchVisible, setIsSearchVisible] = useState(false);

当我们点击搜索按钮的时候,就调用这个setIsSearchVisible函数,通过这个函数来切换搜索框的显示状态,也就是切换isSearchVisible的真假值。

typescript 复制代码
<button onClick={() => setIsSearchVisible(!isSearchVisible)}>
  <AiOutlineSearch size={24} />
</button>

最后,我们根据isSearchVisible的波尔数值来渲染搜索框 。

typescript 复制代码
{isSearchVisible && (
  <input type="text" placeholder="搜索..." />
)}

就是这么简单,一个搜索框就这么灵活地出现在用户眼前了!

为搜索框添加状态

展示搜索框只是基本操作,我们要做的,是让它有细腻的交互体验:

typescript 复制代码
<input
  type="text"
  placeholder="搜索"
  className="px-4 py-2 w-64 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
/>

给搜索框加点 padding 和 宽度,px-4 py-2 w-64;

加上边框,border,使用灰色 border-gray-300

使用大圆角,rounded-lg

使用focus:outline-none来去掉原始浏览器那丑丑的蓝框

添加focus:ring-2 + focus:ring-blue-500:来一圈淡淡的蓝光圈,来提示用户

最后,transition:所有样式变化都带过渡,手感更丝滑

注意,在使用tailwind状态的时候,千万不要在状态名和样式类名之间的冒号旁边加上多余的空格。

进阶:用 peer 做表单交互魔法

你以为这就完了?No!我们再来点进阶玩法------用 Tailwind 的 peer 联动选择器,实现不用 JS 就能动起来的动画输入框!

什么是 peer?

简单理解就是:当一个元素的状态变化时,让它的兄弟元素也跟着一起变化。

比如,我们想实现这样一个效果:

  • 输入框还没点时,提示文字在输入框中间;

  • 一旦聚焦,它就漂移到上面、变小、变蓝!

这个时候就可以用 peer!

第一步:去掉 placeholder,改用 label

typescript 复制代码
<input type="text" className="..." />
<label className="absolute left-2 top-2">搜索</label>

搜索lable使用绝对定位absolute

左移2个单位,上移2个单位,left-2 top-2

第二步:把 input 和 label 一起放进一个 relative 容器中

这样 label 才能通过 absolute 定位在输入框内部:

typescript 复制代码
<div className="relative">
  <input ... />
  <label ...>搜索</label>
</div>

第三步:给 input 添加 peer 类

typescript 复制代码
<input className="peer ..." />

这表示这个input将成为"联动的中心"。

第四步:让 label 根据 input 焦点状态变化

typescript 复制代码
<label
  className="absolute left-2 top-2 text-gray-400
  transition-all
  peer-focus:-top-2
  peer-focus:text-xs
  peer-focus:text-blue-500
  "
>
  搜索
</label>

聚焦时文字上移,使用peer-focus:-top-2

聚焦时让文字变小,peer-focus:text-xs:

聚焦时文字变蓝,peer-focus:text-blue-500:

效果展示

我们的这段代码用 peer 实现了"输入框一聚焦,标签自动让位"的动态浮动效果,让表单也能像苹果原生那样优雅呼吸。这是Tailwind 中"兄弟元素联动"的经典组合,原理很简单但效果非常丝滑:

  • peer 是联动的发起者,输入框声明了自己是个 "peer",意思就是:"我要当焦点时,大家都知道!"
  • 而label 则是联动的响应者,当兄弟元素(就是那个 peer 输入框)处于 focus 状态时,我们的label也做出同时做出改变

默认状态下,label 贴在输入框左上角,看起来像 placeholder;用户一聚焦输入框,label 就开始"变身":上漂、变小、变蓝!。

这整个过程,全靠 peer 实现,完全不需要一行 JS!

小结

在使用Tailwind的状态特效以后,我们写的不再是输入框了,而更像是在打造一段互动对话。

用户每次点击搜索按钮、聚焦输入框,都是在和你的页面"说话"------而通过 Tailwind 的状态变体和 peer,你可以优雅地回应每一句话,堪称输入框中的心灵感应大师。

主题与指令:自定义CSS变量与Tailwind的模式设计

我们今天要聊一个非常实用又带点"高级感"的技巧 ------ Tailwind CSS 的自定义主题与指令(@theme)。有了它,我们不仅可以统一整个项目的颜色、字体、阴影,还能一键支持黑暗模式,像苹果官网一样实现丝滑的 UI 切换体验。

接下来,我们就从自定义指令的定义到实际使用,带你一站式搞懂 Tailwind 的主题扩展的玩法。

先来定义我们的"主题语言"

就像写代码要先定变量,我们做设计也要先定"语法"。在 Tailwind 里,我们可以通过 @theme 指令自定义颜色、字体、阴影等变量,打造一套专属的主题系统。

既然我们开发的是一个仿苹果官网风格的电商网站,那我们的视觉风格也需要使用类似的极简、克制、精致感拉满的苹果风。

打开主 CSS,准备好 Tailwind 环境

第一步,打开 main.css 文件,先导入 Tailwind:

typescript 复制代码
@import "tailwindcss";

简单粗暴,这一行的意思是:"让我用上 Tailwind 的所有原子类,工具函数请通通安排上!"

用 @theme 声明我们的第一组颜色变量

接下来,开始定义主题变量!使用 @theme 这个自定义指令,在其中统一声明所有颜色变量,先定义两个:

typescript 复制代码
@theme {
  /* 主配色 */
  --color-apple-light: #f5f5f7;
  --color-apple-dark: #1d1d1f;
}

第一个给他起名叫做 -color-apple-light:,颜色是f5f5f7,是苹果常用的淡灰背景色,亮色模式下用它最合适。

第二种则是对应的黑暗模式的变量,可以叫做-color-apple-dark,真是颜色取值为 1d1d1f, 是一种深色调的黑灰色,是黑暗模式的理想的背景色。

✍️ 注意:这两个变量前缀是 --,这是 CSS 标准定义变量的写法,和 Sass、Less 里的 $变量名 不一样。

typescript 复制代码
--变量: 值;

怎么用这些变量?让 Tailwind 自动帮我们识别!

Tailwind 会自动帮我们把这些自定义变量变成 工具类,英语叫做 Utility class,只要写成类似 bg-apple-light、dark:bg-apple-dark的形式,它就知道要去取 --color-apple-light 和 --color-apple-dark 的值!

接下来,我们就可以直接在组件中通过主题中的变量名来使用这些颜色了,比如说,请同学们打开MainLayout组件,给它加上className,在这里设置主布局的背景颜色,bg,什么颜色呢?正常模式使用apple-light;黑暗模式下,使用bg + apple-dark 来设置背景颜色。

typescript 复制代码
const MainLayout = ({ header, content, footer }) => (
  <div className="bg-apple-light dark:bg-apple-dark">
...

这个类名结构看起来非常像 Tailwind css的命名规则,实际上tailwind会把你在 @theme 里写的这些变量注册为颜色别名,让你可以这样写:

bg-apple-light → 会自动映射为 background-color: var(--color-apple-light)

dark:bg-apple-dark → 会在暗模式下应用 background-color: var(--color-apple-dark)

这个机制是 @theme + 自定义颜色名(比如 apple-light) → 生成对应的 utility class(bg-apple-light)。

背后原理:

typescript 复制代码
.bg-apple-light {
  background-color: var(--color-apple-light);
}

.dark .bg-apple-dark {
  background-color: var(--color-apple-dark);
}

你可以在亮模式下显示浅灰色背景,暗模式下自动切换为深色背景,这一切通过 class name 实现,无需 JS 切换,完全响应式、纯样式切换。

定义全站颜色系统 🎨

好了,背景颜色搞定,我们接着往下定制整套配色风格。我们按照苹果的设计规范,把常见的颜色还有字体什么的全都定义出来,统一管理,便于后期维护,回到main.css:

typescript 复制代码
@theme {
  /* 主配色 */
  --color-apple-light: #f5f5f7;
  --color-apple-dark: #1d1d1f;
  /* 标准色 */
  --color-apple-black: #000000;
  --color-apple-white: #ffffff;
  /* 品牌配色 */
  --color-apple-blue: #0071e3;
  --color-apple-red: #b64400;
  /* 灰阶系统(gray scale) */
  --color-apple-gray-100: #f5f5f7;
  --color-apple-gray-200: #d2d2d7;
  --color-apple-gray-300: #86868b;
  --color-apple-gray-800: #424245;
  --color-apple-gray-900: #1d1d1f;
  /* 字体 */
  --font-sans: "Inter", "Roboto", "Open Sans", "Helvetica Neue", sans-serif;
  --font-display: "Poppins", "Inter", "sans-serif";
  --font-text: "Source Sans Pro", "Open Sans", "sans-serif";
  /* 字体颜色 */
  --color-apple-text-light: #1d1d1f;
  --color-apple-text-dark: #f5f5f7;
  /* 阴影 */
  --shadow-apple-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
  --shadow-apple-md: 0 4px 12px rgba(0, 0, 0, 0.08);
  --shadow-apple-lg: 0 8px 24px rgba(0, 0, 0, 0.06);
}

修改body,让字体还有颜色什么的全局使用:

typescript 复制代码
body {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0.01em;
  color: var(--color-apple-text-light);
  background-color: var(--color-apple-light);
}

并且在html中引用字体,这条 标签已经打包引用了我们所有需要的全部字体和常用字重:

typescript 复制代码
<link
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&family=Open+Sans:wght@400;600&family=Poppins:wght@500;700&family=Source+Sans+3:wght@400;600&display=swap"
      rel="stylesheet"
    />

family=Inter:wght@400;600 → Inter 字体,权重 400 和 600

&family=Open+Sans... → Open Sans 字体,同理

display=swap → 字体加载策略,建议保留(避免闪跳)

下载后如何使用?

把字体放到项目的 /public/fonts 文件夹下(或你喜欢的位置)

在 CSS 或主样式文件中添加:

typescript 复制代码
/* Inter Regular - 400 */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* Inter SemiBold - 600 */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* Inter Bold - 700 */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

黑暗模式下的文字颜色配置

光背景切换还不够,文字也要能随主题自动变色,比如正文字体:

typescript 复制代码
@theme {
  /* 文字颜色*/
  --color-apple-text-light: #1d1d1f;
  --color-apple-text-dark: #f5f5f7;
}

-color-apple-text-light:白天模式下的深灰字体,适合浅背景。

-color-apple-text-dark:暗模式下的浅色字体,用于深背景下阅读清晰。

黑暗模式:React与Tailwindcss的完美暗黑切换

朋友们,今天我们要解锁一个让前端体验直接封神的神技------用 React + Tailwind CSS 实现『无痛』暗黑模式切换!

Tailwind 黑暗模式使用方法

Tailwind CSS 自带黑暗模式支持,设置起来可以说是既强大又优雅。它的核心逻辑就是:通过一个特殊的 CSS 状态 ------ dark,来"激活"对应的样式。

你可以把它理解为给页面开了个"暗黑滤镜",只要你设置得当,页面就能在白天、黑夜两种模式下自由切换。

Tailwind 的黑暗模式是基于"前缀控制"实现的 ------ 简单说,就是你写样式时,只要前面加上 dark: 前缀,表示"这是黑暗模式下要生效的样式"。

比如下面的代码,当我们切换系统黑暗模式设定的时候,通过对比可以看到文字在不同模式下使用了不同的颜色。

typescript 复制代码
<!-- 文本颜色 -->
<p class="text-gray-900 dark:text-gray-100">
  白天是黑色文字,夜晚是白色文字。
</p>

<!-- 背景色 -->
<div class="bg-white dark:bg-gray-800">
  白天是白色背景,夜晚是深灰背景。
</div>

<!-- 边框颜色 -->
<button class="border-gray-300 dark:border-gray-600">
  按钮边框色随主题切换
</button>
  • text-gray-900 是默认模式(白天)下的字体颜色
  • dark:text-gray-100 是暗黑模式下的字体颜色

所以,Tailwind css中最基本使用方法就是使用前缀dark:

实战暗黑模式

有了前面黑暗模式的基础知识铺垫,我们现在正式开搞,把"黑暗模式"应用到我们网站的顶部导航 ------ 也就是 Header 组件。

这个操作不复杂,甚至可以说:只要手稳、样式写对,几分钟就能搞定一个炫酷的暗黑主题导航条!

在这个改造中,我们的目标很明确:

白天模式:白色背景、深色文字、浅色 Logo,看起来干净清爽

夜间模式:黑色半透明背景、白色文字、亮色 Logo,酷感十足

而这些变化,你只需要动动手指,给一些关键样式加上 dark: 前缀,Tailwind 就能自动帮你处理黑夜和白天的切换。

nav

bg-white/70:白天半透明白色背景

dark:bg-black/90:夜间改为深色半透明背景

Logo 是 svg 文件,这里的处理有点特殊,在黑暗模式下,我们需要使用 fill-white, 这个细节非常重要!因为有些 Logo 是 SVG 的,如果你不改它的 fill 颜色,在黑夜模式下会直接"隐身",啥也看不到。所以,给它加个 dark:fill-white,填充白色。

至于导航菜单的文字部分,加上 dark:text-white 。如果你不加,文字默认颜色就是黑的,那在黑色背景下会直接"隐形",用户根本看不清。

而移动端侧边栏菜单,只要遵守"默认样式 + dark: 覆盖样式"这种写法,整个页面就能做到在浅色和深色之间顺畅切换,毫无违和感。

遮罩层需要做一点点配置:白天是 bg-black/50 比较暗,夜间改为 bg-black/10,让整体观感不至于"全黑",显得沉稳又不过度压抑。

说实话,这整个改造过程并不复杂 ------ 全靠 Tailwind 的 dark: 前缀优雅地控制样式覆盖。

只要你在顶层 HTML 或根组件加了 class="dark",Tailwind 就会自动帮你渲染所有带 dark: 前缀的样式。换句话说:

🌞 白天:白色背景 + 黑字 + 正常 Logo

🌙 夜晚:黑色背景 + 白字 + 亮色 Logo

整个 Header 一秒切换,夜间感瞬间拉满,视觉效果立刻提升一个档次。

手动切换黑暗模式

默认情况下,Tailwind 是"听系统话"的跟随系统设定来切换黑暗和白天模式的,他会通过一个叫做 prefers-color-scheme 的媒体查询来自动判断用户设备的暗黑模式偏好。但问题来了:

"万一用户不想跟随系统,他想自己做主,怎么办?"

答案当然是:给他自由!

要实现手动控制,我们得让 Tailwind 允许我们"人为控制",不再只靠媒体查询。这就需要修改一下我们的 main.css:

typescript 复制代码
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

这行代码的作用是:当 .dark 类存在于任意父元素上时,所有 dark: 样式都将生效。就像我们拿了一个"全局遥控器",谁套上了 .dark,谁就进入暗黑世界。

用 React 实现"一键切换黑夜模式"

接下来,我们要在 React 里整一个"黑夜模式开关"组件,用户点一下,就能瞬间从白天跳转到夜晚,体验感直接拉满!

创建 Dark mode 切换组件

我们新建一个组件叫 DarkToggle,它的核心功能非常简单:

渲染一个图标按钮,点击后在亮色模式和暗色模式之间切换。

为了界面更直观,我们使用一个太阳图标来表示"当前是亮色模式,点击切换为暗色"。当然你也可以换成月亮图标(后面我们扩展),但我们这次先从太阳开始!

先来个最基础的版本:

typescript 复制代码
import { FiSun } from "react-icons/fi";

function DarkToggle() {
  return (
    <button>
      <FiSun size={24} />
    </button>
  );
}

export default DarkToggle;

DarkToggle组件需要返回一个按钮 button。这个组件应该是一个icon button,也就是图标按钮,所以我们在这里从react-icons/fi中引用一个小太阳图标,FiSun,然后在按钮中使用,大小size同样设置为24。

使用 DarkToggle

接下来我们回到 Header 组件,把刚写好的 DarkToggle 放进去,放在右上角、搜索图标旁边,为了和其他按钮对齐,样式统一,我们还需要给最外围的div容器加上横向也就是x轴的间距,space-x-2。

typescript 复制代码
import DarkToggle from "./DarkToggle";

<div className="gap-2 dark:text-white space-x-2">
  <button onClick={() => setIsSearchEnable(!isSearchEnable)}>
    <AiOutlineSearch size={24} />
  </button>
  <DarkToggle />
  <button className="md:hidden" onClick={() => setIsOpen(true)}>
    <AiOutlineMenu size={24} />
  </button>
</div>

UI 美化

现在,按钮显示出来了,但只是显示出来当然不够,我们要做得------有互动感、视觉美、细节到位!

typescript 复制代码
import { FiSun } from "react-icons/fi";

function DarkToggle() {
  return (
    <button
      onClick={toggleDark}
      className="
        p-2 rounded-full
        bg-gray-200 dark:bg-gray-700
        hover:bg-gray-300 dark:hover:bg-gray-600
        transition-all duration-300
        hover:rotate-12  // 悬停旋转,互动感拉满!
      "
    >
      <FiSun className="text-yellow-400 animate-pulse" />
    </button>
  );
}

button 按钮

  • 给button按钮加上内边距,p-2,给图标留出空间,不至于太挤。
  • 按钮圆角设置为最大,rounded-full,按钮就变成了正圆形,这样更像一个图标按钮而不是传统文字按钮。
  • 亮色模式下背景是浅灰色 gray-200,视觉清新
  • 而暗色模式下背景是深灰色 ,通过 Tailwind 的dark前缀来操作,dark:gray-700。
  • 改变一下鼠标悬停时背景颜色,hover:bg-gray-300。而在黑暗模式下则相反,鼠标悬停时背景颜色也应该发生变化,不过变化幅度应该有所不同, dark:hover:bg-gray-600。无论是亮色还是暗色下,都有对应的 hover 反馈,这一步大大将会提升用户交互体验,用户知道"我点到这个按钮了"。
  • 然后使用transition-all duration-300将所有属性的变化(如背景、旋转等)都设置 300 毫秒过渡,实现从灰色到浅灰的渐变过渡,整个过程都非常丝滑!

最后,再来点小心机,添加鼠标悬停hover:rotate-12,悬停时按钮会有一点点旋转,这是前端 UI设计经常使用的 的"小心机",能让这个按钮看起来更有"生命力"。

FiSun 图标

className设置为text-yellow-400,亮黄色,来模拟模拟太阳光感;同时使用一个Tailwind 的内置动画 animate-pulse,图标会有轻微的"呼吸"效果,模拟太阳在闪烁发光的感觉,让按钮更加生动。

实现真正切换逻辑

现在,是时候加入真正的黑暗模式切换逻辑了。打开DarkToggle组件,我们添加一个函数 toggleDark,内部逻辑简单到令人发指:

typescript 复制代码
const toggleDark = () => {
  document.documentElement.classList.toggle("dark");
};

这句代码的意思是:直接给 标签加/删 dark 这个类名。只要这个类一加,页面里所有 dark: 前缀的 Tailwind 样式就会自动触发!这是最直接、最轻量的实现方式,不用状态管理,不需要复杂配置,直接生效!

然后,在button上添加onClick事件,事件触发toggleDark:

typescript 复制代码
import { FiSun } from "react-icons/fi";

function DarkToggle() {
  const toggleDark = () => {
    document.documentElement.classList.toggle("dark");
  };
  return (
    <button
      onClick={toggleDark}
      className="
        p-1 rounded-full
        bg-gray-200 dark:bg-gray-700
        hover:bg-gray-300 dark:hover:bg-gray-600
        transition-all duration-300
        hover:rotate-12 // 悬停旋转,互动感拉满!
      "
    >
      <FiSun className="text-yellow-400 animate-pulse" size={24} />
    </button>
  );
}

export default DarkToggle;

完成其他UI的黑暗模式

今天的课程,我们按照前上节课介绍的内容来启动项目的黑暗模式。

请根据黑暗模式来切换主布局的背景,亮色背景为bg-gray-100,黑暗模式为bg-gray-800

代码比较简单,请同学们自行完成,如需帮助请参考课程代码库。提供一下思路,过程很简单,给想要进行黑暗模式切换的组件添加 class dark,比如:

在主布局MainLayout中添加背景的变化 bg-gray-100 dark:bg-gray-800:

typescript 复制代码
const MainLayout = ({ header, content, footer }) => {
  return (
    <div className="bg-gray-100 dark:bg-gray-800">
      {header ?? <h1>默认标题</h1>}
      {content ?? <p>默认内容</p>}
      {footer ?? <p>默认页脚</p>}
    </div>
  );
};

export default MainLayout;

给 ListTitle 添加文字切换 className="dark:text-white"

我们顺便所以点调整:

  • 给ListTitle加上字体大小,fontSize: 32
  • 给 ProductList 添加 marginTop: "4rem"
typescript 复制代码
//主程序
import React from "react";

const ReleaseNote = () => (
  <div>
    <h1>新产品发布日期:2199年1月1日</h1>
    <h2>请耐心等待</h2>
  </div>
);

const ProductNotFound = () => (
  <div>
    <h1>产品未发布</h1>
    <h2>请耐心等待</h2>
  </div>
);

const ListTitle = ({ title }) => (
  <div style={{ display: "flex", justifyContent: "center" }}>
    <h1
      style={{
        fontSize: 32,
        fontWeight: "800",
        backgroundImage: "url('src/assets/lines.png')",
        backgroundPosition: "center",
      }}
      className="dark:text-white"
    >
      {title || "新品上市"}
    </h1>
  </div>
);

function Productlist({ title, datalength, children }) {
  const isReleased = new Date() <= new Date("2199-01-01");
  if (!isReleased) {
    return <ReleaseNote />;
  }

  if (datalength <= 0) {
    return <ProductNotFound />;
  }

  return (
    <div
      style={{
        marginTop: "4rem",
        display: "grid",
        justifyItems: "center",
        rowGap: "3rem",
      }}
    >
      <ListTitle title={title} />
      {children}
    </div>
  );
}

export default ProductList;

动画系统:在Tailwindcss中打造流畅动画

朋友们,今天我们来探讨 Tailwind CSS 的最后一个话题,动画系统。

作为现代前端开发的核心工具,Tailwind 的动画模块既保持了实用主义的设计哲学,又提供了足够的扩展能力。让我们从基础到进阶,系统地解析它的运作机制。

内置动画:开箱即用的解决方案

Tailwind 提供了大量的内置动画,开箱即用,直接定义在css里就可以使用了,比如说:

typescript 复制代码
<div class="flex h-100 w-full items-center justify-center">
  <div class="h-46 w-46 animate-spin bg-amber-600" />
</div>

animate-spin 是最高频使用的内置动画,采用硬件加速实现平滑旋转

(切换至对比视图)

typescript 复制代码
<!-- 通知提醒动画 -->
<div class="flex h-100 w-full items-center justify-center">
  <div class="relative h-2 w-2">
    <div class="absolute inset-0 animate-ping rounded-full bg-red-500"></div>
  </div>
</div>

animate-ping 通过缩放和透明度变化实现涟漪效果,常用于实时通知提示。它与 transition 类不同,专为短时强反馈设计。"

自定义动画实战:精准控制关键帧

typescript 复制代码
@theme {
  --animate-float: float 4s ease-in-out infinite;
  @keyframes float {
    0%,
    100% {
      transform: translateY(0);
    }
    50% {
      transform: translateY(-10px);
    }
  }
}

自定义动画需要明确定义关键帧和动画参数。这里创建的 slidein 动画实现了元素从右侧滑入的效果,注意 ease-out 时间函数对运动曲线的控制。

typescript 复制代码
const ProductHero = ({ product, imageSrc }) => {
  return (
    ...
    <img className="w-[350px] animate-float" src={imageSrc} />
    ...
  )
}

通过类名直接调用自定义动画,这种方式保持了 Tailwind 声明式开发的特性,避免了 CSS 文件的碎片化。

动画参数:细粒度控制策略

(代码对比演示)

typescript 复制代码
<!-- 基础使用 -->
<div class="animate-spin"></div>

<!-- 参数调优版本 -->
<div class="animate-spin duration-700 delay-200 repeat-2">

"- duration-700 将动画周期延长至 700ms

delay-200 设置 200ms 启动延迟

repeat-2 限制动画执行两次

这种组合特别适合引导用户注意力的场景。"

(性能提示浮窗)

"当同时启用多个动画时,建议添加 will-change 属性优化渲染:"

typescript 复制代码
<div class="animate-slidein will-change-transform"></div>

进阶模式:响应式与状态驱动

悬停时触发脉冲动画

typescript 复制代码
<div class="flex h-100 w-full items-center justify-center">
  <div class="h-46 w-46 bg-amber-600 transition-all hover:animate-pulse active:scale-95" />
</div>

点击时产生微小的缩放反馈

配合 transition-all 确保状态切换平滑

这种分层动效设计能有效提升用户体验。"

(暗黑模式适配)

typescript 复制代码
<div class="animate-spin dark:animate-pulse">

利用 Tailwind 的状态变体,可以针对暗黑模式切换动画类型,实现主题适配的动效方案。

最佳实践与常见误区

(正确示例)

typescript 复制代码
<!-- 正确的不可中断动画 -->
<div class="flex h-100 w-full items-center justify-center">
  <div class="h-46 w-46 animate-spin bg-amber-600 motion-reduce:animate-none" />
</div>

motion-reduce 类会在用户设置减少动效时自动禁用动画,符合无障碍规范。"

(错误警示)

typescript 复制代码
<!-- 反模式:过度使用动画 -->
<div class="flex h-100 w-full items-center justify-center">
  <div class="h-46 w-46 animate-bounce animate-pulse animate-spin bg-amber-600" />
</div>

同时应用多个动画会导致性能问题和视觉混乱,建议通过 JavaScript 控制动画状态切换。

结束语:"掌握 Tailwind 动画的关键,在于平衡实用性与创造性。记住:好的动画应该像 console.log 的输出------只在需要的时候出现,且信息传达清晰明确。下期我们将探讨如何将动画与 React 状态管理结合,实现更智能的交互逻辑。"

相关推荐
我是大卫1 小时前
【图】React源码解析-从源码数据结构、依赖算法、调度流水线到内存管理,深挖useEffect的全部核心机制
react.js·源码
weedsfly1 小时前
前端开发中的代理模式——从 Vue 3 响应式到日常开发
前端·javascript·面试
用户298698530142 小时前
在 React 中通过 JavaScript 实现 Excel 单元格、行与列的锁定保护
javascript·react.js·excel
এ慕ོ冬℘゜2 小时前
深入理解 JavaScript 事件对象:从 target 到 preventDefault 的实战指南
开发语言·前端·javascript
Listen·Rain2 小时前
Vue概述
前端·javascript·vue.js
尘世中一位迷途小书童2 小时前
Cesium 涟漪扩散点:自定义材质,参数调对了才好看
前端·面试
ttwuai2 小时前
Go 后台富文本图片上传失败排查:前端限制、接口和存储目录要一起看
前端·golang·状态模式
小雪_Snow2 小时前
JS 数组的遍历 —— 两种方式详解
前端·javascript
禅思院2 小时前
AI对话前端从入门到崩溃:一个长对话引发的五层优化战争【五】
前端·架构·前端框架