Skip to content

原子化 CSS 框架

什么是原子化 CSS

概念解释

原子化 CSS(Atomic CSS)是一种 CSS 架构方法,它将样式拆分成最小的、不可再分的功能单元(原子类)。每个类名只负责一个单一的样式属性,通过组合多个原子类来构建复杂的 UI 组件。

例如:

html
<!-- 传统 CSS -->
<div class="card">...</div>

<!-- 原子化 CSS -->
<div class="bg-white p-4 rounded-lg shadow-md">...</div>

与传统 CSS 的对比

对比维度传统 CSS原子化 CSS
类名语义语义化(.card, .button)功能化(.p-4, .bg-blue)
CSS 文件大小随项目增长而增长趋于稳定(复用率高)
命名成本需要思考类名无需命名
样式复用通过继承和组合通过类名组合
维护性修改可能影响多处修改局部,影响可控
HTML 体积较小较大(类名多)

优势和适用场景

优势:

  1. CSS 体积可控:随着项目增长,CSS 文件大小趋于平稳
  2. 无命名困扰:不需要为每个组件想类名
  3. 快速开发:直接在 HTML 中编写样式,无需切换文件
  4. 样式复用:原子类天然可复用
  5. 易于维护:样式修改不会产生意外的副作用
  6. 容易删除:删除 HTML 即删除样式,无冗余 CSS

适用场景:

  • 快速原型开发
  • 中大型项目(样式复用率高)
  • 组件化开发(React、Vue 等)
  • 需要快速迭代的项目
  • 团队协作项目(减少样式冲突)

Tailwind CSS

简介

什么是 Tailwind CSS

Tailwind CSS 是一个功能类优先的 CSS 框架,它提供了大量的实用工具类,让你可以直接在 HTML 中快速构建现代化的用户界面。它不像 Bootstrap 那样提供预设计的组件,而是提供底层的工具类,让开发者自由组合。

核心理念(Utility-First)

Utility-First(功能优先)是 Tailwind 的核心理念:

html
<!-- 传统方式 -->
<style>
  .btn-primary {
    background-color: #3490dc;
    color: white;
    padding: 0.5rem 1rem;
    border-radius: 0.25rem;
    font-weight: 700;
  }
</style>
<button class="btn-primary">按钮</button>

<!-- Tailwind 方式 -->
<button class="bg-blue-500 text-white px-4 py-2 rounded font-bold hover:bg-blue-700">
  按钮
</button>

安装和配置

使用 npm/yarn:

bash
# 安装
npm install -D tailwindcss postcss autoprefixer

# 初始化配置文件
npx tailwindcss init -p

配置 tailwind.config.js:

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

在 CSS 文件中引入:

css
/* main.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

核心特性

响应式设计(sm/md/lg/xl/2xl)

Tailwind 使用移动优先的断点系统:

html
<!-- 默认小屏幕单列,中等屏幕双列,大屏幕四列 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
  <div>Item 4</div>
</div>

断点对照表:

断点最小宽度CSS
sm640px@media (min-width: 640px)
md768px@media (min-width: 768px)
lg1024px@media (min-width: 1024px)
xl1280px@media (min-width: 1280px)
2xl1536px@media (min-width: 1536px)

状态变体(hover/focus/active/disabled)

html
<!-- Hover 状态 -->
<button class="bg-blue-500 hover:bg-blue-700 text-white">
  Hover Me
</button>

<!-- Focus 状态 -->
<input class="border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200">

<!-- Active 状态 -->
<button class="bg-blue-500 active:bg-blue-800">
  Click Me
</button>

<!-- Disabled 状态 -->
<button class="bg-blue-500 disabled:bg-gray-300 disabled:cursor-not-allowed" disabled>
  Disabled
</button>

<!-- 组合使用 -->
<a class="text-blue-600 hover:text-blue-800 focus:outline-none focus:ring-2 focus:ring-blue-500">
  Link
</a>

暗色模式

Tailwind 支持两种暗色模式策略:

1. 媒体查询策略(默认):

js
// tailwind.config.js
module.exports = {
  darkMode: 'media', // 使用系统偏好
}
html
<div class="bg-white dark:bg-gray-800 text-black dark:text-white">
  自动跟随系统主题
</div>

2. 类名策略(推荐):

js
// tailwind.config.js
module.exports = {
  darkMode: 'class', // 通过类名控制
}
html
<!-- 在根元素添加 dark 类 -->
<html class="dark">
  <body class="bg-white dark:bg-gray-900">
    <div class="text-gray-900 dark:text-white">
      通过切换 html 的 dark 类控制
    </div>
  </body>
</html>

自定义配置(tailwind.config.js)

js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{html,js,jsx,ts,tsx,vue}'],

  theme: {
    extend: {
      // 扩展颜色
      colors: {
        primary: {
          50: '#f0f9ff',
          100: '#e0f2fe',
          500: '#0ea5e9',
          900: '#0c4a6e',
        },
        brand: '#ff6b6b',
      },

      // 扩展间距
      spacing: {
        '128': '32rem',
        '144': '36rem',
      },

      // 扩展字体
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
        mono: ['Fira Code', 'monospace'],
      },

      // 扩展断点
      screens: {
        '3xl': '1920px',
      },

      // 扩展动画
      animation: {
        'spin-slow': 'spin 3s linear infinite',
      },
    },
  },

  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('@tailwindcss/aspect-ratio'),
  ],
}

JIT 模式

JIT(Just-In-Time)模式是 Tailwind CSS 3.0+ 的默认编译模式,它按需生成样式,而不是预先生成所有可能的组合。

优势:

  1. 更快的构建速度:只生成用到的样式
  2. 开发体验更好:无需等待完整编译
  3. 文件体积更小:生产环境只包含使用的样式
  4. 支持任意值:可以使用任意值而不需要配置

任意值示例:

html
<!-- 使用任意值 -->
<div class="top-[117px]">
<div class="bg-[#1da1f2]">
<div class="text-[14px]">
<div class="w-[762px]">

<!-- 使用 CSS 变量 -->
<div class="bg-[var(--brand-color)]">

<!-- 使用 calc -->
<div class="h-[calc(100vh-4rem)]">

常用类名

布局(flex, grid, container)

html
<!-- Flexbox -->
<div class="flex items-center justify-between">
  <div>Left</div>
  <div>Right</div>
</div>

<div class="flex flex-col md:flex-row gap-4">
  <div class="flex-1">Item 1</div>
  <div class="flex-1">Item 2</div>
</div>

<!-- Grid -->
<div class="grid grid-cols-3 gap-4">
  <div>1</div>
  <div>2</div>
  <div>3</div>
</div>

<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  <!-- Auto-responsive grid -->
</div>

<!-- Container -->
<div class="container mx-auto px-4">
  <!-- 响应式容器,自动居中 -->
</div>

间距(p-4, m-4, space-x-4)

html
<!-- Padding -->
<div class="p-4">所有方向 padding: 1rem</div>
<div class="px-4 py-2">水平 1rem,垂直 0.5rem</div>
<div class="pt-8 pb-4">顶部 2rem,底部 1rem</div>

<!-- Margin -->
<div class="m-4">所有方向 margin: 1rem</div>
<div class="mx-auto">水平居中</div>
<div class="mt-8 mb-4">顶部 2rem,底部 1rem</div>
<div class="-mt-4">负 margin</div>

<!-- Space Between -->
<div class="flex space-x-4">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
</div>

<div class="flex flex-col space-y-2">
  <div>Row 1</div>
  <div>Row 2</div>
</div>

间距比例:

类名RemPixels
p-0000px
p-10.25rem0.25rem4px
p-20.5rem0.5rem8px
p-41rem1rem16px
p-82rem2rem32px
p-164rem4rem64px

颜色(bg-blue-500, text-white)

html
<!-- 背景色 -->
<div class="bg-blue-500">蓝色背景</div>
<div class="bg-gradient-to-r from-purple-500 to-pink-500">渐变背景</div>

<!-- 文字颜色 -->
<p class="text-gray-900">深灰色文字</p>
<p class="text-red-500">红色文字</p>

<!-- 边框颜色 -->
<div class="border border-gray-300">灰色边框</div>

<!-- 不透明度 -->
<div class="bg-blue-500 bg-opacity-50">半透明蓝色</div>
<div class="bg-blue-500/50">半透明蓝色(简写)</div>

颜色深度:

  • 50: 最浅
  • 100-900: 逐渐加深(每次 100)
  • 950: 最深(部分颜色)

字体(text-lg, font-bold)

html
<!-- 字体大小 -->
<p class="text-xs">极小文字 (0.75rem)</p>
<p class="text-sm">小文字 (0.875rem)</p>
<p class="text-base">基础文字 (1rem)</p>
<p class="text-lg">大文字 (1.125rem)</p>
<p class="text-xl">特大文字 (1.25rem)</p>
<p class="text-2xl">2倍大 (1.5rem)</p>
<p class="text-4xl">4倍大 (2.25rem)</p>

<!-- 字重 -->
<p class="font-thin">100</p>
<p class="font-light">300</p>
<p class="font-normal">400</p>
<p class="font-medium">500</p>
<p class="font-semibold">600</p>
<p class="font-bold">700</p>
<p class="font-black">900</p>

<!-- 行高 -->
<p class="leading-none">行高 1</p>
<p class="leading-tight">行高 1.25</p>
<p class="leading-normal">行高 1.5</p>
<p class="leading-loose">行高 2</p>

<!-- 文本对齐 -->
<p class="text-left">左对齐</p>
<p class="text-center">居中</p>
<p class="text-right">右对齐</p>
<p class="text-justify">两端对齐</p>

<!-- 文本装饰 -->
<p class="underline">下划线</p>
<p class="line-through">删除线</p>
<p class="no-underline">无装饰</p>

边框(border, rounded-lg)

html
<!-- 边框宽度 -->
<div class="border">1px 边框</div>
<div class="border-2">2px 边框</div>
<div class="border-4">4px 边框</div>
<div class="border-t">仅顶部边框</div>
<div class="border-x-2">左右边框 2px</div>

<!-- 圆角 -->
<div class="rounded">圆角 0.25rem</div>
<div class="rounded-md">中等圆角 0.375rem</div>
<div class="rounded-lg">大圆角 0.5rem</div>
<div class="rounded-xl">特大圆角 0.75rem</div>
<div class="rounded-full">完全圆形</div>
<div class="rounded-t-lg">仅顶部圆角</div>

<!-- 阴影 -->
<div class="shadow-sm">小阴影</div>
<div class="shadow">默认阴影</div>
<div class="shadow-md">中等阴影</div>
<div class="shadow-lg">大阴影</div>
<div class="shadow-xl">特大阴影</div>
<div class="shadow-2xl">超大阴影</div>
<div class="shadow-none">无阴影</div>

代码示例

卡片组件

html
<!-- 基础卡片 -->
<div class="max-w-sm rounded overflow-hidden shadow-lg">
  <img class="w-full" src="/img/card-top.jpg" alt="Sunset in the mountains">
  <div class="px-6 py-4">
    <div class="font-bold text-xl mb-2">卡片标题</div>
    <p class="text-gray-700 text-base">
      这是卡片的描述内容,可以包含多行文字。
    </p>
  </div>
  <div class="px-6 pt-4 pb-2">
    <span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">
      #标签1
    </span>
    <span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">
      #标签2
    </span>
  </div>
</div>

<!-- 交互式卡片 -->
<div class="max-w-sm rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300 bg-white">
  <div class="relative">
    <img class="w-full h-48 object-cover" src="/img/product.jpg" alt="Product">
    <div class="absolute top-0 right-0 bg-red-500 text-white px-2 py-1 m-2 rounded-md text-sm font-semibold">
      SALE
    </div>
  </div>
  <div class="p-6">
    <h3 class="font-bold text-xl mb-2 text-gray-800">产品名称</h3>
    <p class="text-gray-600 text-sm mb-4">
      产品的详细描述信息...
    </p>
    <div class="flex items-center justify-between">
      <span class="text-2xl font-bold text-gray-900">$99.99</span>
      <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full transition-colors duration-200">
        加入购物车
      </button>
    </div>
  </div>
</div>

导航栏

html
<!-- 响应式导航栏 -->
<nav class="bg-white shadow-lg">
  <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
    <div class="flex justify-between h-16">
      <!-- Logo -->
      <div class="flex items-center">
        <img class="h-8 w-auto" src="/logo.svg" alt="Logo">
        <span class="ml-2 text-xl font-bold text-gray-800">Brand</span>
      </div>

      <!-- Desktop Menu -->
      <div class="hidden md:flex items-center space-x-8">
        <a href="#" class="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium transition-colors">
          首页
        </a>
        <a href="#" class="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium transition-colors">
          产品
        </a>
        <a href="#" class="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium transition-colors">
          关于
        </a>
        <a href="#" class="bg-blue-500 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors">
          登录
        </a>
      </div>

      <!-- Mobile menu button -->
      <div class="md:hidden flex items-center">
        <button class="text-gray-700 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500">
          <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
          </svg>
        </button>
      </div>
    </div>
  </div>

  <!-- Mobile Menu -->
  <div class="md:hidden">
    <div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
      <a href="#" class="text-gray-700 hover:bg-gray-100 block px-3 py-2 rounded-md text-base font-medium">
        首页
      </a>
      <a href="#" class="text-gray-700 hover:bg-gray-100 block px-3 py-2 rounded-md text-base font-medium">
        产品
      </a>
      <a href="#" class="text-gray-700 hover:bg-gray-100 block px-3 py-2 rounded-md text-base font-medium">
        关于
      </a>
    </div>
  </div>
</nav>

表单

html
<!-- 登录表单 -->
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
  <div class="max-w-md w-full space-y-8">
    <!-- Header -->
    <div>
      <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
        登录您的账户
      </h2>
      <p class="mt-2 text-center text-sm text-gray-600">

        <a href="#" class="font-medium text-blue-600 hover:text-blue-500">
          注册新账户
        </a>
      </p>
    </div>

    <!-- Form -->
    <form class="mt-8 space-y-6" action="#" method="POST">
      <div class="rounded-md shadow-sm space-y-4">
        <!-- Email -->
        <div>
          <label for="email" class="block text-sm font-medium text-gray-700 mb-1">
            邮箱地址
          </label>
          <input
            id="email"
            name="email"
            type="email"
            required
            class="appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
            placeholder="请输入邮箱"
          >
        </div>

        <!-- Password -->
        <div>
          <label for="password" class="block text-sm font-medium text-gray-700 mb-1">
            密码
          </label>
          <input
            id="password"
            name="password"
            type="password"
            required
            class="appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
            placeholder="请输入密码"
          >
        </div>
      </div>

      <!-- Remember & Forgot -->
      <div class="flex items-center justify-between">
        <div class="flex items-center">
          <input
            id="remember-me"
            name="remember-me"
            type="checkbox"
            class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
          >
          <label for="remember-me" class="ml-2 block text-sm text-gray-900">
            记住我
          </label>
        </div>

        <div class="text-sm">
          <a href="#" class="font-medium text-blue-600 hover:text-blue-500">
            忘记密码?
          </a>
        </div>
      </div>

      <!-- Submit Button -->
      <div>
        <button
          type="submit"
          class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200"
        >
          登录
        </button>
      </div>
    </form>
  </div>
</div>

响应式布局

html
<!-- 响应式网格布局 -->
<div class="container mx-auto px-4 py-8">
  <!-- 标题 -->
  <h1 class="text-3xl md:text-4xl lg:text-5xl font-bold text-center mb-8 text-gray-900">
    响应式产品展示
  </h1>

  <!-- 产品网格 -->
  <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
    <!-- 产品卡片 -->
    <div class="bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300">
      <img src="/product1.jpg" alt="Product" class="w-full h-48 object-cover rounded-t-lg">
      <div class="p-4">
        <h3 class="text-lg font-semibold text-gray-800 mb-2">产品标题</h3>
        <p class="text-gray-600 text-sm mb-4 line-clamp-2">
          产品描述文字...
        </p>
        <div class="flex items-center justify-between">
          <span class="text-xl font-bold text-blue-600">$99</span>
          <button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded text-sm transition-colors">
            购买
          </button>
        </div>
      </div>
    </div>

    <!-- 重复更多卡片... -->
  </div>

  <!-- 两列布局:侧边栏 + 主内容 -->
  <div class="mt-12 grid grid-cols-1 lg:grid-cols-4 gap-8">
    <!-- 侧边栏 (移动端全宽,桌面端占1列) -->
    <aside class="lg:col-span-1 bg-gray-50 p-6 rounded-lg">
      <h2 class="text-xl font-bold mb-4">分类</h2>
      <ul class="space-y-2">
        <li><a href="#" class="text-blue-600 hover:underline">电子产品</a></li>
        <li><a href="#" class="text-blue-600 hover:underline">家居用品</a></li>
        <li><a href="#" class="text-blue-600 hover:underline">服装配饰</a></li>
      </ul>
    </aside>

    <!-- 主内容 (移动端全宽,桌面端占3列) -->
    <main class="lg:col-span-3">
      <div class="bg-white p-6 rounded-lg shadow">
        <h2 class="text-2xl font-bold mb-4">主要内容区域</h2>
        <p class="text-gray-700 leading-relaxed">
          这里是主要内容区域,在移动端会显示在侧边栏下方,
          在桌面端会显示在侧边栏右侧,占据更多空间。
        </p>
      </div>
    </main>
  </div>
</div>

优缺点分析

优点:

  1. 生态系统成熟

    • 完善的文档和教程
    • 丰富的插件系统
    • 大量的社区资源和模板
  2. 开发效率高

    • 无需命名 CSS 类
    • 快速原型开发
    • 直观的类名系统
  3. 可维护性好

    • 样式与 HTML 紧密结合
    • 删除 HTML 即删除样式
    • 减少 CSS 冗余
  4. 性能优秀

    • JIT 模式按需生成
    • 生产环境自动压缩
    • Tree-shaking 移除未使用样式
  5. 自定义能力强

    • 灵活的配置系统
    • 支持自定义工具类
    • 可扩展的设计系统

缺点:

  1. HTML 类名冗长

    • 复杂组件类名可能很长
    • HTML 可读性降低
    • 代码审查不够直观
  2. 学习曲线

    • 需要记忆大量类名
    • 初期开发速度慢
    • 需要理解命名规则
  3. 样式复用困难

    • 重复的类名组合
    • 需要配合组件化解决
    • 或使用 @apply 指令
  4. 调试相对困难

    • DevTools 中看到的都是工具类
    • 不如语义化类名直观
    • 需要适应新的调试方式
  5. 团队协作挑战

    • 需要团队统一规范
    • 新成员学习成本
    • 可能产生不一致的实现

UnoCSS

简介

什么是 UnoCSS

UnoCSS 是一个即时按需的原子化 CSS 引擎,而不是一个传统的 CSS 框架。它由 Anthony Fu 开发,设计理念是提供极致的灵活性和性能,让开发者可以完全自定义自己的原子化 CSS 解决方案。

即时原子化引擎

UnoCSS 的核心特点是"即时"(Instant):

js
// 传统方式:预生成所有可能的类
// bg-red-100, bg-red-200, ..., bg-blue-100, bg-blue-200...

// UnoCSS:扫描代码,只生成使用的类
// 你用了 bg-red-500,它就只生成这一个

工作原理:

  1. 扫描源代码
  2. 提取类名
  3. 按需生成 CSS
  4. 极致的性能和体积优化

与 Tailwind 的关系

UnoCSS 可以看作是 Tailwind CSS 的精神继承者,但有本质区别:

对比项Tailwind CSSUnoCSS
定位CSS 框架CSS 引擎
预设固定的工具类集合完全可定制的预设
性能快(JIT 模式)更快(5-200 倍)
灵活性通过配置扩展完全可编程
体积中等极小
兼容性可使用 preset-wind原生支持多种预设

核心特性

预设系统

UnoCSS 通过预设系统提供灵活的配置:

typescript
// uno.config.ts
import { defineConfig, presetUno, presetAttributify, presetIcons } from 'unocss'

export default defineConfig({
  presets: [
    presetUno(), // Tailwind / Windi CSS 预设
    presetAttributify(), // 属性化模式
    presetIcons(), // 图标预设
  ],
})

主要预设:

  1. @unocss/preset-uno

    • 默认预设,包含常用工具类
    • 兼容 Tailwind CSS 和 Windi CSS
  2. @unocss/preset-wind

    • 完全兼容 Tailwind CSS
    • 适合从 Tailwind 迁移的项目
  3. @unocss/preset-icons

    • 直接使用任何图标集
    • 支持 100+ 图标库
html
<!-- 使用图标预设 -->
<div class="i-carbon-sun"></div>
<div class="i-mdi-account"></div>
<div class="i-heroicons-outline-home"></div>
  1. @unocss/preset-attributify
    • 属性化模式,将类名拆分为属性
html
<!-- 传统方式 -->
<div class="bg-blue-500 text-white p-4 rounded-lg"></div>

<!-- 属性化模式 -->
<div
  bg="blue-500"
  text="white"
  p="4"
  rounded="lg"
></div>

属性模式

属性模式让 HTML 更加清晰可读:

html
<!-- 基础用法 -->
<button
  bg="blue-500 hover:blue-700"
  text="white sm"
  font="bold"
  py="2"
  px="4"
  rounded
>
  按钮
</button>

<!-- 响应式 -->
<div
  grid="~ cols-1 md:cols-2 lg:cols-4"
  gap="4"
>
  <div>Item</div>
</div>

<!-- 值属性 -->
<div
  m="x-auto"
  p="t-4 b-2 x-6"
  text="center lg"
  font="bold"
>
  内容
</div>

变体组

变体组功能可以减少重复的前缀:

html
<!-- 传统写法 -->
<div class="hover:bg-blue-500 hover:text-white hover:scale-110">

<!-- 变体组写法 -->
<div class="hover:(bg-blue-500 text-white scale-110)">

<!-- 复杂示例 -->
<div class="
  dark:(bg-gray-800 text-white border-gray-700)
  lg:(grid grid-cols-3 gap-8)
  hover:(shadow-xl scale-105)
">
  内容
</div>

快捷方式

快捷方式允许你定义常用的类名组合:

typescript
// uno.config.ts
export default defineConfig({
  shortcuts: {
    // 静态快捷方式
    'btn': 'px-4 py-2 rounded font-semibold bg-blue-500 text-white hover:bg-blue-700',
    'btn-green': 'px-4 py-2 rounded font-semibold bg-green-500 text-white hover:bg-green-700',

    // 动态快捷方式
    'btn': 'px-4 py-2 rounded font-semibold transition-colors',

    // 使用数组
    'card': [
      'bg-white',
      'rounded-lg',
      'shadow-md',
      'p-6',
      'hover:shadow-xl',
      'transition-shadow',
    ],

    // 使用函数(动态)
    'btn-color': ([, color]) => `bg-${color}-500 hover:bg-${color}-700 text-white`,
  },
})
html
<!-- 使用快捷方式 -->
<button class="btn">默认按钮</button>
<button class="btn-green">绿色按钮</button>
<div class="card">卡片内容</div>
<button class="btn-color-red">红色按钮</button>

图标支持

UnoCSS 的图标预设是杀手级特性:

bash
# 安装
npm i -D @unocss/preset-icons @iconify-json/[collection-name]

# 或安装所有图标集(不推荐,体积大)
npm i -D @iconify/json
typescript
// uno.config.ts
import { defineConfig, presetIcons } from 'unocss'

export default defineConfig({
  presets: [
    presetIcons({
      scale: 1.2, // 缩放
      warn: true, // 警告缺失的图标
      extraProperties: {
        'display': 'inline-block',
        'vertical-align': 'middle',
      },
    }),
  ],
})
html
<!-- 使用图标 -->
<div class="i-carbon-sun"></div>
<div class="i-mdi-github text-2xl"></div>
<div class="i-heroicons-outline-home text-blue-500"></div>

<!-- 自定义颜色和大小 -->
<div class="i-carbon-user text-red-500 text-3xl"></div>

<!-- 在按钮中使用 -->
<button class="flex items-center gap-2">
  <div class="i-carbon-save"></div>
  <span>保存</span>
</button>

常用图标集:

  • @iconify-json/carbon - Carbon Design
  • @iconify-json/mdi - Material Design Icons
  • @iconify-json/heroicons - Heroicons
  • @iconify-json/tabler - Tabler Icons
  • @iconify-json/lucide - Lucide Icons

配置示例

typescript
// uno.config.ts
import {
  defineConfig,
  presetUno,
  presetAttributify,
  presetIcons,
  presetTypography,
  presetWebFonts,
  transformerDirectives,
  transformerVariantGroup,
} from 'unocss'

export default defineConfig({
  // 预设
  presets: [
    presetUno(),
    presetAttributify(),
    presetIcons({
      scale: 1.2,
      cdn: 'https://esm.sh/',
    }),
    presetTypography(),
    presetWebFonts({
      fonts: {
        sans: 'Inter:400,600,800',
        mono: 'Fira Code',
      },
    }),
  ],

  // 快捷方式
  shortcuts: {
    'btn': 'px-4 py-2 rounded inline-block bg-blue-500 text-white cursor-pointer hover:bg-blue-700 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50',
    'btn-sm': 'text-sm px-3 py-1',
    'btn-lg': 'text-lg px-6 py-3',
    'card': 'bg-white rounded-lg shadow-md p-6 hover:shadow-xl transition-shadow',
    'input': 'border border-gray-300 rounded px-3 py-2 focus:outline-none focus:border-blue-500',
  },

  // 自定义规则
  rules: [
    // 自定义规则:m-<num>px -> margin: <num>px
    [/^m-(\d+)px$/, ([, d]) => ({ margin: `${d}px` })],

    // 自定义规则:grid-<num> -> grid-template-columns: repeat(<num>, 1fr)
    [/^grid-(\d+)$/, ([, d]) => ({ 'grid-template-columns': `repeat(${d}, 1fr)` })],

    // 自定义规则:text-shadow
    ['text-shadow', { 'text-shadow': '0 2px 4px rgba(0,0,0,0.1)' }],
  ],

  // 主题
  theme: {
    colors: {
      primary: {
        DEFAULT: '#3490dc',
        dark: '#2779bd',
      },
      secondary: '#ffed4e',
    },
    breakpoints: {
      xs: '320px',
      sm: '640px',
      md: '768px',
      lg: '1024px',
      xl: '1280px',
      '2xl': '1536px',
    },
  },

  // 转换器
  transformers: [
    transformerDirectives(), // 启用 @apply, @screen 等指令
    transformerVariantGroup(), // 启用变体组功能
  ],

  // 安全列表(始终包含这些类)
  safelist: [
    'i-carbon-sun',
    'i-carbon-moon',
  ],

  // 提取器配置
  content: {
    pipeline: {
      include: [
        /\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/,
      ],
    },
  },
})

Vite 项目中使用:

typescript
// vite.config.ts
import { defineConfig } from 'vite'
import UnoCSS from 'unocss/vite'

export default defineConfig({
  plugins: [
    UnoCSS(),
  ],
})
typescript
// main.ts
import 'virtual:uno.css'

代码示例

html
<!-- 基础示例 -->
<div class="
  flex items-center justify-between
  p-4 m-2
  bg-gradient-to-r from-blue-500 to-purple-500
  rounded-lg shadow-lg
  hover:(shadow-2xl scale-105)
  transition-all duration-300
">
  <span class="text-white font-bold">UnoCSS Card</span>
  <div class="i-carbon-arrow-right text-white text-2xl"></div>
</div>

<!-- 属性化模式 -->
<div
  flex="~ col md:row"
  items="center"
  justify="between"
  p="4"
  bg="white dark:gray-800"
  border="~ gray-200 dark:gray-700"
  rounded="lg"
  shadow="md hover:xl"
  transition="all duration-300"
>
  <h3 text="xl" font="bold" class="dark:text-white">标题</h3>
  <button
    px="4"
    py="2"
    bg="blue-500 hover:blue-700"
    text="white"
    rounded
    transition="colors"
  >
    点击
  </button>
</div>

<!-- 使用快捷方式 -->
<div class="card">
  <h2 class="text-2xl font-bold mb-4">卡片标题</h2>
  <p class="text-gray-600 mb-4">卡片内容</p>
  <button class="btn">按钮</button>
</div>

<!-- 图标示例 -->
<div class="flex gap-4 text-2xl">
  <div class="i-carbon-sun text-yellow-500"></div>
  <div class="i-carbon-moon text-blue-500"></div>
  <div class="i-mdi-github text-gray-800"></div>
  <div class="i-heroicons-outline-home text-green-500"></div>
</div>

<!-- 响应式网格 -->
<div class="
  grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3
  gap-4 p-4
">
  <div class="card">卡片 1</div>
  <div class="card">卡片 2</div>
  <div class="card">卡片 3</div>
</div>

<!-- 变体组复杂示例 -->
<div class="
  group
  relative overflow-hidden
  bg-white
  rounded-xl
  shadow-lg
  transition-all duration-300
  hover:(shadow-2xl scale-105)
  dark:(bg-gray-800 border border-gray-700)
">
  <div class="absolute inset-0 bg-gradient-to-r from-blue-500 to-purple-500 opacity-0 group-hover:opacity-10 transition-opacity"></div>
  <div class="relative p-6">
    <div class="flex items-center gap-3 mb-4">
      <div class="i-carbon-data-vis-4 text-3xl text-blue-500"></div>
      <h3 class="text-xl font-bold dark:text-white">数据可视化</h3>
    </div>
    <p class="text-gray-600 dark:text-gray-300">
      使用 UnoCSS 快速构建美观的界面
    </p>
  </div>
</div>

优缺点分析

优点:

  1. 性能极佳

    • 启动速度快 5-200 倍(相比 Tailwind JIT)
    • 热更新速度极快
    • 按需生成,零额外开销
  2. 体积极小

    • 核心包不到 5KB
    • 只生成使用的样式
    • 无运行时依赖
  3. 灵活性极强

    • 完全可编程的 API
    • 自定义规则简单
    • 支持任意预设组合
  4. 图标集成优秀

    • 内置图标预设
    • 支持 100+ 图标库
    • 使用简单,按需加载
  5. 属性化模式

    • HTML 更清晰
    • 更好的可读性
    • 可选功能,不强制使用
  6. 工具生态

    • VS Code 插件支持
    • DevTools 支持
    • Inspector 工具

缺点:

  1. 生态相对较小

    • 社区规模小于 Tailwind
    • 第三方资源较少
    • 模板和示例不够丰富
  2. 学习资源有限

    • 中文文档相对较少
    • 教程和课程不多
    • 需要一定的学习成本
  3. 配置复杂度

    • 高度自定义需要配置
    • 自定义规则需要学习
    • 对新手不够友好
  4. 团队采用风险

    • 相对较新的项目
    • 团队熟悉度低
    • 可能需要培训
  5. 兼容性考虑

    • 需要构建工具支持
    • 某些老项目迁移成本高

Tailwind CSS vs UnoCSS 对比

详细对比表格

特性Tailwind CSSUnoCSS
性能快(JIT 模式)极快(5-200 倍)
启动时间1-3 秒<100ms
热更新速度极快(几乎即时)
体积~40KB(核心)<5KB(核心)
构建体积按需生成,已优化更小,极致优化
灵活性通过配置扩展完全可编程
自定义规则支持,通过插件原生支持,更简单
预设系统固定框架可组合预设
生态系统非常成熟快速发展中
插件数量100+ 官方和社区插件20+ 预设和插件
社区规模超大中等
学习曲线中等中等到陡峭
文档质量优秀良好
中文文档完善基本完善
VS Code 支持官方插件,功能完善官方插件,功能丰富
图标支持需要第三方库内置,100+ 图标集
属性化模式不支持原生支持
变体组不支持原生支持
快捷方式通过 @apply原生配置
框架支持Vue/React/Angular/SvelteVue/React/Angular/Svelte
构建工具PostCSSVite/Webpack/Rollup
配置文件tailwind.config.jsuno.config.ts
TypeScript类型支持原生 TS,类型完善
运行时
兼容性可从 v2 升级到 v3向后兼容
迁移难度中等(从其他框架)简单(从 Tailwind)
团队采用广泛采用,风险低新兴技术,需评估
企业支持Tailwind Labs开源社区
更新频率稳定更新活跃更新
稳定性非常稳定稳定
浏览器支持现代浏览器现代浏览器
SSR 支持完善完善
价格免费(UI 库收费)完全免费

性能对比

冷启动时间:
Tailwind JIT: ~1000ms
UnoCSS:       ~50ms

热更新时间:
Tailwind JIT: ~50ms
UnoCSS:       ~5ms

构建时间(1000个组件):
Tailwind:     ~3s
UnoCSS:       ~0.5s

最终 CSS 体积:
Tailwind:     ~15KB (gzipped)
UnoCSS:       ~12KB (gzipped)

如何选择

适合使用 Tailwind 的场景

  1. 大型企业项目

    • 需要稳定可靠的解决方案
    • 团队已熟悉 Tailwind
    • 有大量现有 Tailwind 资源
  2. 需要丰富生态

    • 需要大量第三方插件
    • 使用 Tailwind UI 等商业产品
    • 需要大量社区模板
  3. 团队技能考虑

    • 团队成员熟悉 Tailwind
    • 不想投入学习成本
    • 需要快速上手
  4. 长期维护项目

    • 需要长期技术支持
    • 担心新技术的稳定性
    • 企业风险控制要求
  5. 现有项目迁移

    • 已有 Tailwind 项目
    • 迁移成本高
    • 不急于优化性能

适合使用 UnoCSS 的场景

  1. 性能敏感项目

    • 需要极致的构建性能
    • 大型应用,组件数量多
    • 开发体验要求高
  2. 高度定制需求

    • 需要自定义大量规则
    • 设计系统复杂
    • 需要灵活的配置
  3. 新项目

    • 从零开始的项目
    • 可以尝试新技术
    • 团队愿意学习
  4. 需要图标集成

    • 大量使用图标
    • 需要多个图标库
    • 希望简化图标使用
  5. Vue 生态

    • Vue 3 项目
    • Vite 构建工具
    • Nuxt 3 项目
  6. 追求极致优化

    • 对体积和性能有极致要求
    • 喜欢探索新技术
    • 技术驱动的团队

决策流程图

开始
├─ 是否为新项目?
│  ├─ 是 → 团队愿意学习新技术?
│  │      ├─ 是 → 考虑 UnoCSS
│  │      └─ 否 → 使用 Tailwind
│  └─ 否 → 已使用 Tailwind?
│         ├─ 是 → 性能是否是瓶颈?
│         │      ├─ 是 → 评估迁移到 UnoCSS
│         │      └─ 否 → 继续使用 Tailwind
│         └─ 否 → 需要快速上手?
│                ├─ 是 → 使用 Tailwind
│                └─ 否 → 对比选择

原子化 CSS 最佳实践

组件抽象

虽然原子化 CSS 提倡直接在 HTML 中使用工具类,但在实际项目中,我们仍然需要组件抽象来避免重复。

1. 使用组件框架

vue
<!-- Button.vue -->
<template>
  <button
    :class="[
      'px-4 py-2 rounded font-semibold transition-colors',
      variantClasses[variant],
      sizeClasses[size],
      disabled && 'opacity-50 cursor-not-allowed'
    ]"
    :disabled="disabled"
  >
    <slot />
  </button>
</template>

<script setup>
const props = defineProps({
  variant: {
    type: String,
    default: 'primary',
    validator: (value) => ['primary', 'secondary', 'danger'].includes(value)
  },
  size: {
    type: String,
    default: 'md',
    validator: (value) => ['sm', 'md', 'lg'].includes(value)
  },
  disabled: Boolean
})

const variantClasses = {
  primary: 'bg-blue-500 hover:bg-blue-700 text-white',
  secondary: 'bg-gray-500 hover:bg-gray-700 text-white',
  danger: 'bg-red-500 hover:bg-red-700 text-white'
}

const sizeClasses = {
  sm: 'text-sm px-3 py-1',
  md: 'text-base px-4 py-2',
  lg: 'text-lg px-6 py-3'
}
</script>

<!-- 使用 -->
<Button variant="primary" size="lg">大号主按钮</Button>
<Button variant="danger" size="sm" disabled>小号禁用按钮</Button>

2. 使用 @apply 指令

css
/* components.css */
@layer components {
  .btn {
    @apply px-4 py-2 rounded font-semibold transition-colors;
  }

  .btn-primary {
    @apply bg-blue-500 hover:bg-blue-700 text-white;
  }

  .btn-secondary {
    @apply bg-gray-500 hover:bg-gray-700 text-white;
  }

  .card {
    @apply bg-white rounded-lg shadow-md p-6 hover:shadow-xl transition-shadow;
  }

  .input {
    @apply w-full px-3 py-2 border border-gray-300 rounded-md
           focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent;
  }
}
html
<!-- 使用抽象后的类 -->
<button class="btn btn-primary">主按钮</button>
<div class="card">卡片内容</div>
<input type="text" class="input" placeholder="输入框">

3. 使用快捷方式(UnoCSS)

typescript
// uno.config.ts
export default defineConfig({
  shortcuts: {
    // 按钮系列
    'btn-base': 'px-4 py-2 rounded font-semibold transition-colors cursor-pointer',
    'btn-primary': 'btn-base bg-blue-500 hover:bg-blue-700 text-white',
    'btn-secondary': 'btn-base bg-gray-500 hover:bg-gray-700 text-white',

    // 输入框
    'input-base': 'w-full px-3 py-2 border rounded-md focus:outline-none transition-colors',
    'input-normal': 'input-base border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200',
    'input-error': 'input-base border-red-500 focus:border-red-600 focus:ring-2 focus:ring-red-200',

    // 布局
    'flex-center': 'flex items-center justify-center',
    'flex-between': 'flex items-center justify-between',

    // 卡片
    'card': 'bg-white rounded-lg shadow-md p-6 hover:shadow-xl transition-shadow',
  }
})

与组件库结合

1. 自定义组件库主题

js
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        // 品牌色
        brand: {
          50: '#eff6ff',
          100: '#dbeafe',
          500: '#3b82f6',
          900: '#1e3a8a',
        }
      }
    }
  },
  // 如果使用 Element Plus 等组件库
  content: [
    './src/**/*.{vue,js,ts}',
    './node_modules/element-plus/**/*.{js,ts,vue}', // 扫描组件库
  ],
}

2. 包装组件库组件

vue
<!-- MyButton.vue -->
<template>
  <el-button
    :class="customClasses"
    v-bind="$attrs"
  >
    <slot />
  </el-button>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({
  customStyle: String
})

const customClasses = computed(() => {
  return [
    'shadow-md hover:shadow-lg transition-all',
    props.customStyle
  ]
})
</script>

3. 混合使用策略

vue
<template>
  <div class="max-w-4xl mx-auto p-6">
    <!-- 使用组件库 -->
    <el-form class="bg-white rounded-lg shadow-md p-6">
      <!-- 用 Tailwind 增强样式 -->
      <el-form-item class="mb-6">
        <el-input
          class="focus:ring-2 focus:ring-blue-500 rounded-md"
          placeholder="请输入"
        />
      </el-form-item>

      <!-- 完全自定义的按钮 -->
      <button class="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg transition-colors">
        提交
      </button>
    </el-form>
  </div>
</template>

性能优化

1. 按需加载

js
// tailwind.config.js
module.exports = {
  // 只扫描需要的文件
  content: [
    './src/**/*.{vue,js,ts,jsx,tsx}',
    './public/index.html',
    // 不扫描 node_modules(除非必要)
  ],

  // 生产环境优化
  purge: {
    enabled: process.env.NODE_ENV === 'production',
    content: ['./src/**/*.{vue,js,ts,jsx,tsx}'],
  },
}

2. 使用 PurgeCSS

js
// postcss.config.js
module.exports = {
  plugins: [
    require('tailwindcss'),
    require('autoprefixer'),
    process.env.NODE_ENV === 'production' &&
      require('@fullhuman/postcss-purgecss')({
        content: ['./src/**/*.{vue,js,ts,jsx,tsx}'],
        defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
      })
  ]
}

3. 代码分割

js
// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'vendor': ['vue', 'vue-router'],
          'ui': ['element-plus'],
        }
      }
    }
  }
}

4. 懒加载样式

vue
<script setup>
import { defineAsyncComponent } from 'vue'

// 异步加载重组件
const HeavyComponent = defineAsyncComponent(() =>
  import('./HeavyComponent.vue')
)
</script>

5. 避免过度嵌套

html
<!-- 不推荐:过度嵌套 -->
<div class="flex">
  <div class="w-full">
    <div class="p-4">
      <div class="text-center">
        内容
      </div>
    </div>
  </div>
</div>

<!-- 推荐:扁平化 -->
<div class="flex w-full p-4 text-center">
  内容
</div>

6. 使用生产构建

bash
# 开发环境
npm run dev

# 生产构建
npm run build

# 构建分析
npm run build -- --report

7. 监控和优化

js
// 使用 webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
    })
  ]
}

面试题

1. 什么是原子化CSS,有什么优势?

答案:

原子化 CSS(Atomic CSS)是一种 CSS 架构方法,将样式拆分成最小的、单一用途的功能类(原子类)。每个类只负责一个样式属性,通过组合多个原子类来构建复杂的 UI。

核心概念:

  • 每个类名只做一件事(如 p-4 只设置 padding)
  • 类名直接描述其功能(功能优先,而非语义优先)
  • 通过组合类名构建组件

主要优势:

  1. CSS 体积可控

    • 随着项目增长,新增功能不再需要新增 CSS
    • 样式复用率极高,CSS 文件大小趋于稳定
    • 生产环境可以做到极致的体积优化
  2. 开发效率提升

    • 无需为每个组件命名类名
    • 无需在 HTML 和 CSS 文件间切换
    • 快速原型开发,所见即所得
  3. 可维护性强

    • 样式与 HTML 紧密耦合,修改影响范围可控
    • 删除 HTML 即删除样式,无冗余 CSS
    • 不会产生意外的样式覆盖
  4. 一致性好

    • 使用设计系统的预定义值
    • 减少"魔法数字"
    • 团队风格统一
  5. 易于协作

    • 降低命名冲突
    • 代码审查更聚焦功能
    • 新成员快速上手

示例对比:

html
<!-- 传统 CSS -->
<style>
.user-card {
  padding: 16px;
  margin: 8px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>
<div class="user-card">...</div>

<!-- 原子化 CSS -->
<div class="p-4 m-2 bg-white rounded-lg shadow-md">...</div>

2. Tailwind CSS 的 JIT 模式是什么?

答案:

JIT(Just-In-Time)模式是 Tailwind CSS 3.0+ 的默认编译模式,它采用"按需生成"策略,只在需要时生成使用到的样式,而不是预先生成所有可能的组合。

工作原理:

  1. 扫描阶段:扫描源代码,提取类名
  2. 生成阶段:只生成实际使用的类的 CSS
  3. 增量更新:文件变化时,只重新生成变化的部分

核心优势:

  1. 极快的构建速度

    传统模式:生成所有可能的组合 → 3-5s 启动
    JIT 模式:按需生成 → <100ms 启动
  2. 开发体验提升

    • 热更新速度极快(几乎即时)
    • 无需等待完整编译
    • 支持任意值
  3. 文件体积更小

    • 开发环境 CSS 体积大幅减少
    • 生产环境只包含使用的样式
    • 无需额外的 PurgeCSS 配置
  4. 支持任意值

    html
    <!-- JIT 模式支持任意值 -->
    <div class="top-[117px]">
    <div class="bg-[#1da1f2]">
    <div class="text-[14px]">
    <div class="w-[762px]">
    <div class="h-[calc(100vh-4rem)]">
    
    <!-- 使用 CSS 变量 -->
    <div class="bg-[var(--brand-color)]">
  5. 所有变体默认启用

    html
    <!-- 传统模式需要配置才能使用某些变体 -->
    <!-- JIT 模式所有变体都可用 -->
    <div class="hover:rotate-180">
    <div class="focus:scale-125">
    <div class="active:brightness-90">

配置(Tailwind 3.0+ 默认启用):

js
// tailwind.config.js
module.exports = {
  // Tailwind 3.0+ 默认就是 JIT 模式
  content: [
    './src/**/*.{html,js,vue,jsx,tsx}',
  ],
  // ...
}

传统模式 vs JIT 模式:

对比项传统模式JIT 模式
启动时间3-5s<100ms
开发环境 CSS 体积3-10MB几KB
热更新速度较慢极快
任意值支持不支持支持
变体支持需配置全部可用
生产环境配置需要 PurgeCSS自动优化

3. UnoCSS 相比 Tailwind 有什么优势?

答案:

UnoCSS 是下一代原子化 CSS 引擎,相比 Tailwind CSS 有以下显著优势:

1. 性能优势(最大优势)

性能对比:
冷启动:Tailwind JIT ~1000ms vs UnoCSS ~50ms (20倍)
热更新:Tailwind ~50ms vs UnoCSS ~5ms (10倍)
构建速度:UnoCSS 快 5-200 倍
  • 即时编译:真正的按需生成,零预构建
  • 极快的 HMR:文件变化时几乎即时更新
  • 体积更小:核心包 <5KB vs Tailwind ~40KB

2. 高度灵活和可定制

typescript
// UnoCSS 可以完全自定义规则
export default defineConfig({
  rules: [
    // 自定义任意规则
    [/^m-(\d+)px$/, ([, d]) => ({ margin: `${d}px` })],
    ['flex-center', { display: 'flex', 'justify-content': 'center', 'align-items': 'center' }],
  ]
})
  • 完全可编程:使用 TypeScript 配置,类型安全
  • 预设系统:可组合多个预设(Tailwind、Windi、Bootstrap 等)
  • 自定义规则简单:添加规则只需几行代码

3. 原生图标支持(杀手级功能)

html
<!-- 直接使用 100+ 图标库 -->
<div class="i-carbon-sun"></div>
<div class="i-mdi-github"></div>
<div class="i-heroicons-outline-home"></div>

<!-- 自定义大小和颜色 -->
<div class="i-carbon-user text-2xl text-red-500"></div>
  • 内置图标预设,支持 Iconify 所有图标集
  • 按需加载,只打包使用的图标
  • 无需额外配置,开箱即用

4. 属性化模式

html
<!-- 传统方式 -->
<div class="bg-blue-500 text-white p-4 rounded-lg hover:bg-blue-700"></div>

<!-- UnoCSS 属性化模式 -->
<div
  bg="blue-500 hover:blue-700"
  text="white"
  p="4"
  rounded="lg"
></div>
  • HTML 更清晰易读
  • 相关样式分组
  • 可选功能,不强制使用

5. 变体组功能

html
<!-- 传统写法 -->
<div class="hover:bg-blue-500 hover:text-white hover:scale-110"></div>

<!-- UnoCSS 变体组 -->
<div class="hover:(bg-blue-500 text-white scale-110)"></div>

<!-- 复杂示例 -->
<div class="dark:(bg-gray-800 text-white) lg:(grid grid-cols-3)"></div>
  • 减少重复前缀
  • 代码更简洁
  • 逻辑更清晰

6. 快捷方式更强大

typescript
shortcuts: {
  // 静态
  'btn': 'px-4 py-2 rounded bg-blue-500 text-white',

  // 动态(支持参数)
  'btn-color': ([, color]) => `bg-${color}-500 hover:bg-${color}-700`,
}

7. 更小的生产包体积

典型项目对比:
Tailwind CSS: ~15KB (gzipped)
UnoCSS:       ~10KB (gzipped)

8. 开发工具优秀

  • Inspector:可视化查看生成的样式
  • VS Code 插件:智能提示、预览
  • DevTools:调试工具

9. 框架集成更好

  • Vite 原生支持:专为 Vite 优化
  • Nuxt 模块:Nuxt 3 一键集成
  • 更好的 SSR:服务端渲染优化

劣势(需要注意):

  1. 生态相对较小:社区资源不如 Tailwind 丰富
  2. 学习资源有限:教程和文档相对较少
  3. 配置复杂度:高度自定义需要学习成本
  4. 团队采用风险:较新的技术,需要团队评估

选择建议:

  • 选 Tailwind:需要稳定性、丰富生态、团队熟悉度
  • 选 UnoCSS:追求性能、高度定制、新项目、Vue 生态

4. 如何解决原子化CSS类名过长的问题?

答案:

原子化 CSS 的类名过长是常见问题,但有多种成熟的解决方案:

1. 组件抽象(最推荐)

将重复的类名组合封装成组件:

vue
<!-- Button.vue -->
<template>
  <button :class="buttonClasses">
    <slot />
  </button>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({
  variant: { type: String, default: 'primary' },
  size: { type: String, default: 'md' }
})

const buttonClasses = computed(() => {
  const base = 'px-4 py-2 rounded font-semibold transition-colors'
  const variants = {
    primary: 'bg-blue-500 hover:bg-blue-700 text-white',
    secondary: 'bg-gray-500 hover:bg-gray-700 text-white'
  }
  const sizes = {
    sm: 'text-sm px-3 py-1',
    md: 'text-base px-4 py-2',
    lg: 'text-lg px-6 py-3'
  }
  return `${base} ${variants[props.variant]} ${sizes[props.size]}`
})
</script>

<!-- 使用 -->
<Button variant="primary" size="lg">登录</Button>

2. 使用 @apply 指令

css
/* styles/components.css */
@layer components {
  .btn {
    @apply px-4 py-2 rounded font-semibold transition-colors
           bg-blue-500 hover:bg-blue-700 text-white
           focus:outline-none focus:ring-2 focus:ring-blue-500;
  }

  .card {
    @apply bg-white rounded-lg shadow-md p-6
           hover:shadow-xl transition-shadow;
  }

  .input-text {
    @apply w-full px-3 py-2 border border-gray-300 rounded-md
           focus:outline-none focus:ring-2 focus:ring-blue-500
           focus:border-transparent;
  }
}
html
<!-- 使用抽象后的类 -->
<button class="btn">按钮</button>
<div class="card">卡片内容</div>
<input type="text" class="input-text">

3. 使用快捷方式(UnoCSS)

typescript
// uno.config.ts
export default defineConfig({
  shortcuts: {
    // 静态快捷方式
    'btn-primary': 'px-4 py-2 rounded font-semibold bg-blue-500 hover:bg-blue-700 text-white transition-colors',
    'card': 'bg-white rounded-lg shadow-md p-6 hover:shadow-xl transition-shadow',

    // 动态快捷方式
    'flex-center': 'flex items-center justify-center',
    'flex-between': 'flex items-center justify-between',

    // 数组形式(更清晰)
    'btn': [
      'px-4', 'py-2', 'rounded',
      'font-semibold',
      'transition-colors',
      'focus:outline-none',
      'focus:ring-2',
    ],
  }
})

4. 使用变体组(UnoCSS)

html
<!-- 优化前:重复的 hover 前缀 -->
<div class="hover:bg-blue-500 hover:text-white hover:scale-110 hover:shadow-lg">

<!-- 优化后:使用变体组 -->
<div class="hover:(bg-blue-500 text-white scale-110 shadow-lg)">

<!-- 复杂示例 -->
<div class="
  dark:(bg-gray-800 text-white border-gray-700)
  lg:(grid grid-cols-3 gap-8 px-12)
  md:(grid-cols-2 gap-6 px-8)
">

5. 使用属性化模式(UnoCSS)

html
<!-- 优化前:所有样式在 class 中 -->
<div class="flex items-center justify-between p-4 bg-white dark:bg-gray-800 rounded-lg shadow-md">

<!-- 优化后:属性化模式 -->
<div
  flex="~ items-center justify-between"
  p="4"
  bg="white dark:gray-800"
  rounded="lg"
  shadow="md"
>

6. 格式化和换行

html
<!-- 不推荐:一行太长 -->
<div class="flex items-center justify-between p-4 m-2 bg-white dark:bg-gray-800 rounded-lg shadow-md hover:shadow-xl transition-all duration-300">

<!-- 推荐:合理换行 -->
<div class="
  flex items-center justify-between
  p-4 m-2
  bg-white dark:bg-gray-800
  rounded-lg
  shadow-md hover:shadow-xl
  transition-all duration-300
">

<!-- 或使用数组(Vue/React) -->
<div :class="[
  'flex items-center justify-between',
  'p-4 m-2',
  'bg-white dark:bg-gray-800',
  'rounded-lg shadow-md hover:shadow-xl',
  'transition-all duration-300'
]">

7. 使用 CSS Modules + 原子类(混合方案)

vue
<template>
  <div :class="$style.card">
    <h3 :class="$style.title">标题</h3>
    <p class="text-gray-600 mb-4">内容</p>
    <button class="btn-primary">按钮</button>
  </div>
</template>

<style module>
.card {
  @apply bg-white rounded-lg shadow-md p-6;
  @apply hover:shadow-xl transition-shadow;
}

.title {
  @apply text-2xl font-bold mb-4 text-gray-900;
}
</style>

8. 使用 clsx/classnames 库

jsx
import clsx from 'clsx'

function Button({ variant, size, disabled, children }) {
  return (
    <button className={clsx(
      // 基础样式
      'px-4 py-2 rounded font-semibold transition-colors',
      // 变体样式
      {
        'bg-blue-500 hover:bg-blue-700 text-white': variant === 'primary',
        'bg-gray-500 hover:bg-gray-700 text-white': variant === 'secondary',
      },
      // 尺寸样式
      {
        'text-sm px-3 py-1': size === 'sm',
        'text-lg px-6 py-3': size === 'lg',
      },
      // 状态样式
      disabled && 'opacity-50 cursor-not-allowed'
    )}>
      {children}
    </button>
  )
}

最佳实践建议:

  1. 分层策略

    • 高频复用组件:封装成组件
    • 中频样式:使用 @apply 或快捷方式
    • 低频样式:直接使用原子类
  2. 团队约定

    • 制定类名排序规则(布局 → 间距 → 颜色 → 其他)
    • 使用 Prettier 或 ESLint 插件自动排序
    • 统一换行规范
  3. 工具辅助

    • 使用 VS Code 插件:Tailwind CSS IntelliSense
    • 使用自动排序工具:prettier-plugin-tailwindcss
    • 使用代码片段(snippets)

5. 原子化CSS与CSS-in-JS的区别?

答案:

原子化 CSS 和 CSS-in-JS 是两种不同的 CSS 解决方案,各有特点和适用场景。

核心理念差异:

维度原子化 CSSCSS-in-JS
样式定义预定义的工具类动态生成的样式
样式位置HTML 类名JavaScript 对象/模板字符串
运行时无运行时(纯 CSS)有运行时(JS 生成 CSS)
类型静态 CSS动态 CSS

1. 原子化 CSS

html
<!-- Tailwind CSS 示例 -->
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow-md">
  <h3 class="text-xl font-bold text-gray-900">标题</h3>
  <button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-700">
    按钮
  </button>
</div>

特点:

  • 样式通过类名组合
  • 编译时生成 CSS
  • 无运行时开销
  • 类名可复用

2. CSS-in-JS

jsx
// styled-components 示例
import styled from 'styled-components'

const Card = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem;
  background: white;
  border-radius: 0.5rem;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
`

const Title = styled.h3`
  font-size: 1.25rem;
  font-weight: bold;
  color: #111;
`

const Button = styled.button`
  padding: 0.5rem 1rem;
  background: ${props => props.primary ? '#3b82f6' : '#6b7280'};
  color: white;
  border-radius: 0.25rem;

  &:hover {
    background: ${props => props.primary ? '#2563eb' : '#4b5563'};
  }
`

// 使用
<Card>
  <Title>标题</Title>
  <Button primary>按钮</Button>
</Card>

特点:

  • 样式在 JavaScript 中定义
  • 运行时生成 CSS
  • 支持动态样式
  • 组件作用域

详细对比:

性能对比:

初始加载:
原子化 CSS: 15-30KB CSS(一次性)
CSS-in-JS:   20-50KB JS + 运行时开销

运行时性能:
原子化 CSS: 无运行时,纯 CSS
CSS-in-JS:   需要 JS 解析和注入 CSS

构建性能:
原子化 CSS: 快速(预编译)
CSS-in-JS:   较慢(需要 JS 处理)

语法对比:

jsx
// 原子化 CSS(Tailwind)
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-700">
  按钮
</button>

// CSS-in-JS(styled-components)
const Button = styled.button`
  padding: 0.5rem 1rem;
  background: #3b82f6;
  color: white;
  border-radius: 0.25rem;
  &:hover {
    background: #2563eb;
  }
`
<Button>按钮</Button>

// CSS-in-JS(Emotion)
<button css={{
  padding: '0.5rem 1rem',
  background: '#3b82f6',
  color: 'white',
  borderRadius: '0.25rem',
  '&:hover': {
    background: '#2563eb'
  }
}}>
  按钮
</button>

动态样式对比:

jsx
// 原子化 CSS - 使用条件类名
<button className={`
  px-4 py-2 rounded
  ${primary ? 'bg-blue-500 hover:bg-blue-700' : 'bg-gray-500 hover:bg-gray-700'}
  ${size === 'large' ? 'text-lg px-6 py-3' : 'text-sm px-3 py-1'}
`}>
  按钮
</button>

// CSS-in-JS - 使用 props
const Button = styled.button`
  padding: ${props => props.size === 'large' ? '0.75rem 1.5rem' : '0.25rem 0.75rem'};
  background: ${props => props.primary ? '#3b82f6' : '#6b7280'};
  font-size: ${props => props.size === 'large' ? '1.125rem' : '0.875rem'};

  &:hover {
    background: ${props => props.primary ? '#2563eb' : '#4b5563'};
  }
`

<Button primary size="large">按钮</Button>

优缺点对比:

原子化 CSS 优点:

  1. 无运行时开销
  2. CSS 体积可控
  3. 开发效率高
  4. 性能优秀
  5. 易于缓存

原子化 CSS 缺点:

  1. HTML 类名可能很长
  2. 学习曲线(需记忆类名)
  3. 动态样式相对困难
  4. 样式复用需要组件化

CSS-in-JS 优点:

  1. 完全的动态能力
  2. 组件作用域(无全局污染)
  3. 类型安全(TypeScript)
  4. 主题切换方便
  5. 样式与逻辑共存

CSS-in-JS 缺点:

  1. 运行时开销
  2. 性能相对较差
  3. 增加包体积
  4. SSR 配置复杂
  5. 样式复用不如原子类

适用场景:

选择原子化 CSS:

  • 追求极致性能
  • 快速开发和迭代
  • 设计系统规范
  • 静态站点生成
  • 大型项目(样式复用率高)

选择 CSS-in-JS:

  • 需要高度动态的样式
  • 复杂的主题系统
  • 组件库开发
  • 需要完全的类型安全
  • 运行时计算样式

混合使用:

jsx
// 结合两者优势
import styled from 'styled-components'

// 使用 CSS-in-JS 做复杂逻辑
const DynamicCard = styled.div`
  background: ${props => {
    if (props.status === 'success') return '#10b981'
    if (props.status === 'error') return '#ef4444'
    return '#6b7280'
  }};

  // 复杂动画
  animation: ${props => props.animate && fadeIn} 0.3s ease-in;
`

// 使用原子类做基础样式
<DynamicCard status="success" className="p-6 rounded-lg shadow-lg">
  内容
</DynamicCard>

结论:

两者不是对立关系,而是互补关系。在实际项目中,可以根据具体需求选择:

  • 大部分情况:原子化 CSS(性能好、开发快)
  • 特殊场景:CSS-in-JS(复杂动态逻辑)
  • 最佳实践:两者结合使用