本章概述

本章将深入探讨 UnoCSS 的核心特性之一:自定义规则与变体系统。我们将学习如何创建自己的CSS规则、变体和快捷方式,以满足特定项目需求和设计系统要求。

规则系统基础

什么是规则

在 UnoCSS 中,规则(Rules)是将类名转换为CSS样式的核心机制。每个规则定义了如何匹配类名并生成对应的CSS。

// 基本规则结构
const rules = [
  // [匹配器, CSS生成器]
  ['m-1', { margin: '0.25rem' }],
  ['p-2', { padding: '0.5rem' }],
  
  // 动态规则
  [/^m-(.+)$/, ([, d]) => ({ margin: `${d}px` })],
  [/^p-(.+)$/, ([, d]) => ({ padding: `${d}px` })],
]

规则类型

1. 静态规则

// 静态规则 - 固定的类名对应固定的样式
const staticRules = [
  // 基础样式
  ['flex', { display: 'flex' }],
  ['block', { display: 'block' }],
  ['hidden', { display: 'none' }],
  
  // 定位
  ['relative', { position: 'relative' }],
  ['absolute', { position: 'absolute' }],
  ['fixed', { position: 'fixed' }],
  
  // 文本对齐
  ['text-center', { 'text-align': 'center' }],
  ['text-left', { 'text-align': 'left' }],
  ['text-right', { 'text-align': 'right' }],
  
  // 自定义工具类
  ['btn', {
    'padding': '0.5rem 1rem',
    'border-radius': '0.25rem',
    'font-weight': '500',
    'cursor': 'pointer',
    'transition': 'all 0.2s',
  }],
  
  ['card', {
    'background-color': 'white',
    'border-radius': '0.5rem',
    'box-shadow': '0 1px 3px 0 rgba(0, 0, 0, 0.1)',
    'padding': '1.5rem',
  }],
]

2. 动态规则

// 动态规则 - 使用正则表达式匹配并生成样式
const dynamicRules = [
  // 间距规则
  [/^m-(.+)$/, ([, d]) => ({ margin: `${d}px` })],
  [/^p-(.+)$/, ([, d]) => ({ padding: `${d}px` })],
  [/^mt-(.+)$/, ([, d]) => ({ 'margin-top': `${d}px` })],
  [/^mb-(.+)$/, ([, d]) => ({ 'margin-bottom': `${d}px` })],
  [/^ml-(.+)$/, ([, d]) => ({ 'margin-left': `${d}px` })],
  [/^mr-(.+)$/, ([, d]) => ({ 'margin-right': `${d}px` })],
  
  // 尺寸规则
  [/^w-(.+)$/, ([, d]) => ({ width: `${d}px` })],
  [/^h-(.+)$/, ([, d]) => ({ height: `${d}px` })],
  [/^max-w-(.+)$/, ([, d]) => ({ 'max-width': `${d}px` })],
  [/^min-h-(.+)$/, ([, d]) => ({ 'min-height': `${d}px` })],
  
  // 颜色规则
  [/^text-(.+)$/, ([, color]) => ({ color: `var(--color-${color})` })],
  [/^bg-(.+)$/, ([, color]) => ({ 'background-color': `var(--color-${color})` })],
  [/^border-(.+)$/, ([, color]) => ({ 'border-color': `var(--color-${color})` })],
  
  // 字体大小
  [/^text-(.+)px$/, ([, size]) => ({ 'font-size': `${size}px` })],
  [/^text-(.+)rem$/, ([, size]) => ({ 'font-size': `${size}rem` })],
  
  // 圆角
  [/^rounded-(.+)$/, ([, radius]) => ({ 'border-radius': `${radius}px` })],
  
  // 阴影
  [/^shadow-(.+)$/, ([, level]) => {
    const shadows = {
      'sm': '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
      'md': '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
      'lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
      'xl': '0 20px 25px -5px rgba(0, 0, 0, 0.1)',
    }
    return { 'box-shadow': shadows[level] || `0 ${level}px ${level * 2}px rgba(0, 0, 0, 0.1)` }
  }],
]

3. 函数式规则

// 函数式规则 - 更复杂的逻辑处理
const functionalRules = [
  // 网格系统
  [/^grid-cols-(.+)$/, ([, cols]) => {
    if (cols === 'none') return { 'grid-template-columns': 'none' }
    if (cols === 'subgrid') return { 'grid-template-columns': 'subgrid' }
    return { 'grid-template-columns': `repeat(${cols}, minmax(0, 1fr))` }
  }],
  
  // 弹性布局
  [/^flex-(.+)$/, ([, value]) => {
    if (value === 'auto') return { flex: '1 1 auto' }
    if (value === 'initial') return { flex: '0 1 auto' }
    if (value === 'none') return { flex: 'none' }
    return { flex: value }
  }],
  
  // 渐变背景
  [/^bg-gradient-(.+)$/, ([, direction]) => {
    const directions = {
      'to-r': 'to right',
      'to-l': 'to left',
      'to-t': 'to top',
      'to-b': 'to bottom',
      'to-tr': 'to top right',
      'to-tl': 'to top left',
      'to-br': 'to bottom right',
      'to-bl': 'to bottom left',
    }
    return {
      'background-image': `linear-gradient(${directions[direction] || direction}, var(--un-gradient-stops))`
    }
  }],
  
  // 动画
  [/^animate-(.+)$/, ([, name]) => {
    const animations = {
      'spin': 'spin 1s linear infinite',
      'ping': 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
      'pulse': 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
      'bounce': 'bounce 1s infinite',
      'fade-in': 'fadeIn 0.5s ease-in-out',
      'slide-up': 'slideUp 0.3s ease-out',
    }
    return { animation: animations[name] || name }
  }],
  
  // 变换
  [/^transform-(.+)$/, ([, transform]) => {
    const transforms = {
      'none': 'none',
      'gpu': 'translate3d(0, 0, 0)',
    }
    return { transform: transforms[transform] || transform }
  }],
  
  // 过渡
  [/^transition-(.+)$/, ([, property]) => {
    const properties = {
      'none': 'none',
      'all': 'all 150ms cubic-bezier(0.4, 0, 0.2, 1)',
      'colors': 'color, background-color, border-color, text-decoration-color, fill, stroke 150ms cubic-bezier(0.4, 0, 0.2, 1)',
      'opacity': 'opacity 150ms cubic-bezier(0.4, 0, 0.2, 1)',
      'shadow': 'box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1)',
      'transform': 'transform 150ms cubic-bezier(0.4, 0, 0.2, 1)',
    }
    return { transition: properties[property] || `${property} 150ms cubic-bezier(0.4, 0, 0.2, 1)` }
  }],
]

变体系统详解

什么是变体

变体(Variants)是UnoCSS中用于修改规则应用条件的机制,如响应式断点、伪类、伪元素等。

// 变体基本结构
const variants = [
  // [匹配器, 变体处理器]
  (matcher) => {
    if (!matcher.startsWith('hover:')) return matcher
    return {
      matcher: matcher.slice(6), // 移除 'hover:' 前缀
      selector: s => `${s}:hover`, // 添加 :hover 伪类
    }
  },
]

内置变体类型

1. 伪类变体

// 伪类变体
const pseudoClassVariants = [
  // 交互状态
  (matcher) => {
    const pseudoClasses = {
      'hover:': ':hover',
      'focus:': ':focus',
      'active:': ':active',
      'visited:': ':visited',
      'disabled:': ':disabled',
      'checked:': ':checked',
      'focus-within:': ':focus-within',
      'focus-visible:': ':focus-visible',
    }
    
    for (const [prefix, pseudo] of Object.entries(pseudoClasses)) {
      if (matcher.startsWith(prefix)) {
        return {
          matcher: matcher.slice(prefix.length),
          selector: s => `${s}${pseudo}`,
        }
      }
    }
    return matcher
  },
  
  // 结构伪类
  (matcher) => {
    const structuralPseudos = {
      'first:': ':first-child',
      'last:': ':last-child',
      'odd:': ':nth-child(odd)',
      'even:': ':nth-child(even)',
      'first-of-type:': ':first-of-type',
      'last-of-type:': ':last-of-type',
      'only:': ':only-child',
      'only-of-type:': ':only-of-type',
      'empty:': ':empty',
    }
    
    for (const [prefix, pseudo] of Object.entries(structuralPseudos)) {
      if (matcher.startsWith(prefix)) {
        return {
          matcher: matcher.slice(prefix.length),
          selector: s => `${s}${pseudo}`,
        }
      }
    }
    return matcher
  },
]

2. 伪元素变体

// 伪元素变体
const pseudoElementVariants = [
  (matcher) => {
    const pseudoElements = {
      'before:': '::before',
      'after:': '::after',
      'first-line:': '::first-line',
      'first-letter:': '::first-letter',
      'selection:': '::selection',
      'placeholder:': '::placeholder',
      'backdrop:': '::backdrop',
    }
    
    for (const [prefix, pseudo] of Object.entries(pseudoElements)) {
      if (matcher.startsWith(prefix)) {
        return {
          matcher: matcher.slice(prefix.length),
          selector: s => `${s}${pseudo}`,
        }
      }
    }
    return matcher
  },
]

3. 响应式变体

// 响应式变体
const responsiveVariants = [
  (matcher) => {
    const breakpoints = {
      'sm:': '640px',
      'md:': '768px',
      'lg:': '1024px',
      'xl:': '1280px',
      '2xl:': '1536px',
    }
    
    for (const [prefix, size] of Object.entries(breakpoints)) {
      if (matcher.startsWith(prefix)) {
        return {
          matcher: matcher.slice(prefix.length),
          parent: `@media (min-width: ${size})`,
        }
      }
    }
    return matcher
  },
  
  // 最大宽度断点
  (matcher) => {
    const maxBreakpoints = {
      'max-sm:': '639px',
      'max-md:': '767px',
      'max-lg:': '1023px',
      'max-xl:': '1279px',
    }
    
    for (const [prefix, size] of Object.entries(maxBreakpoints)) {
      if (matcher.startsWith(prefix)) {
        return {
          matcher: matcher.slice(prefix.length),
          parent: `@media (max-width: ${size})`,
        }
      }
    }
    return matcher
  },
]

4. 暗色模式变体

// 暗色模式变体
const darkModeVariants = [
  (matcher) => {
    if (!matcher.startsWith('dark:')) return matcher
    return {
      matcher: matcher.slice(5),
      parent: '@media (prefers-color-scheme: dark)',
    }
  },
  
  // 基于类的暗色模式
  (matcher) => {
    if (!matcher.startsWith('dark:')) return matcher
    return {
      matcher: matcher.slice(5),
      selector: s => `.dark ${s}`,
    }
  },
]

自定义变体

1. 简单自定义变体

// 自定义变体示例
const customVariants = [
  // 打印样式
  (matcher) => {
    if (!matcher.startsWith('print:')) return matcher
    return {
      matcher: matcher.slice(6),
      parent: '@media print',
    }
  },
  
  // 高对比度模式
  (matcher) => {
    if (!matcher.startsWith('high-contrast:')) return matcher
    return {
      matcher: matcher.slice(14),
      parent: '@media (prefers-contrast: high)',
    }
  },
  
  // 减少动画
  (matcher) => {
    if (!matcher.startsWith('motion-safe:')) return matcher
    return {
      matcher: matcher.slice(12),
      parent: '@media (prefers-reduced-motion: no-preference)',
    }
  },
  
  // 自定义伪类
  (matcher) => {
    if (!matcher.startsWith('group-hover:')) return matcher
    return {
      matcher: matcher.slice(12),
      selector: s => `.group:hover ${s}`,
    }
  },
  
  // 方向变体
  (matcher) => {
    const directions = {
      'ltr:': '[dir="ltr"]',
      'rtl:': '[dir="rtl"]',
    }
    
    for (const [prefix, selector] of Object.entries(directions)) {
      if (matcher.startsWith(prefix)) {
        return {
          matcher: matcher.slice(prefix.length),
          selector: s => `${selector} ${s}`,
        }
      }
    }
    return matcher
  },
]

2. 复杂自定义变体

// 复杂变体示例
const advancedVariants = [
  // 容器查询
  (matcher) => {
    const containerMatch = matcher.match(/^@container-(.+):/)
    if (!containerMatch) return matcher
    
    const [, query] = containerMatch
    return {
      matcher: matcher.slice(containerMatch[0].length),
      parent: `@container ${query}`,
    }
  },
  
  // 支持查询
  (matcher) => {
    const supportsMatch = matcher.match(/^supports-(.+):/)
    if (!supportsMatch) return matcher
    
    const [, feature] = supportsMatch
    const features = {
      'grid': '(display: grid)',
      'flex': '(display: flex)',
      'backdrop-blur': '(backdrop-filter: blur(0px))',
      'css-variables': '(--css: variables)',
    }
    
    return {
      matcher: matcher.slice(supportsMatch[0].length),
      parent: `@supports ${features[feature] || `(${feature})`}`,
    }
  },
  
  // 数据属性变体
  (matcher) => {
    const dataMatch = matcher.match(/^data-(.+):/)
    if (!dataMatch) return matcher
    
    const [, attr] = dataMatch
    return {
      matcher: matcher.slice(dataMatch[0].length),
      selector: s => `${s}[data-${attr}]`,
    }
  },
  
  // 状态变体
  (matcher) => {
    const stateMatch = matcher.match(/^state-(.+):/)
    if (!stateMatch) return matcher
    
    const [, state] = stateMatch
    return {
      matcher: matcher.slice(stateMatch[0].length),
      selector: s => `${s}[data-state="${state}"]`,
    }
  },
]

快捷方式系统

什么是快捷方式

快捷方式(Shortcuts)允许你将多个工具类组合成一个简单的类名,类似于组件样式。

// 基本快捷方式
const shortcuts = {
  // 按钮样式
  'btn': 'px-4 py-2 rounded font-medium cursor-pointer transition-colors',
  'btn-primary': 'btn bg-blue-500 text-white hover:bg-blue-600',
  'btn-secondary': 'btn bg-gray-200 text-gray-800 hover:bg-gray-300',
  'btn-danger': 'btn bg-red-500 text-white hover:bg-red-600',
  
  // 卡片样式
  'card': 'bg-white rounded-lg shadow-md p-6',
  'card-hover': 'card hover:shadow-lg transition-shadow',
  
  // 输入框样式
  'input': 'px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500',
  'input-error': 'input border-red-500 focus:ring-red-500',
  
  // 布局
  'container': 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8',
  'flex-center': 'flex items-center justify-center',
  'flex-between': 'flex items-center justify-between',
}

动态快捷方式

// 动态快捷方式
const dynamicShortcuts = [
  // 按钮尺寸
  [/^btn-(.+)$/, ([, size]) => {
    const sizes = {
      'xs': 'px-2 py-1 text-xs',
      'sm': 'px-3 py-1.5 text-sm',
      'md': 'px-4 py-2 text-base',
      'lg': 'px-6 py-3 text-lg',
      'xl': 'px-8 py-4 text-xl',
    }
    return `btn ${sizes[size] || sizes.md}`
  }],
  
  // 网格布局
  [/^grid-(.+)$/, ([, cols]) => {
    return `grid grid-cols-${cols} gap-4`
  }],
  
  // 间距组合
  [/^space-(.+)$/, ([, size]) => {
    return `space-x-${size} space-y-${size}`
  }],
  
  // 文本样式
  [/^text-(.+)-(.+)$/, ([, size, weight]) => {
    return `text-${size} font-${weight}`
  }],
]

条件快捷方式

// 条件快捷方式
const conditionalShortcuts = [
  // 主题相关
  {
    'btn-theme': (context) => {
      const isDark = context.theme.dark
      return isDark 
        ? 'btn bg-gray-800 text-white hover:bg-gray-700'
        : 'btn bg-white text-gray-800 hover:bg-gray-100'
    }
  },
  
  // 响应式快捷方式
  {
    'responsive-text': 'text-sm sm:text-base md:text-lg lg:text-xl',
    'responsive-padding': 'p-4 sm:p-6 md:p-8 lg:p-12',
    'responsive-grid': 'grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4',
  },
]

完整配置示例

企业级配置

// uno.config.js
import { defineConfig } from 'unocss'

export default defineConfig({
  // 自定义规则
  rules: [
    // 静态规则
    ['flex-center', { display: 'flex', 'align-items': 'center', 'justify-content': 'center' }],
    ['flex-between', { display: 'flex', 'align-items': 'center', 'justify-content': 'space-between' }],
    ['absolute-center', { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }],
    
    // 动态规则
    [/^m-(.+)$/, ([, d]) => ({ margin: `${d}px` })],
    [/^p-(.+)$/, ([, d]) => ({ padding: `${d}px` })],
    [/^text-(.+)px$/, ([, size]) => ({ 'font-size': `${size}px` })],
    [/^leading-(.+)$/, ([, height]) => ({ 'line-height': height })],
    [/^tracking-(.+)$/, ([, spacing]) => ({ 'letter-spacing': `${spacing}em` })],
    
    // 颜色规则
    [/^text-brand-(.+)$/, ([, shade]) => ({ color: `var(--brand-${shade})` })],
    [/^bg-brand-(.+)$/, ([, shade]) => ({ 'background-color': `var(--brand-${shade})` })],
    [/^border-brand-(.+)$/, ([, shade]) => ({ 'border-color': `var(--brand-${shade})` })],
    
    // 阴影规则
    [/^shadow-brand-(.+)$/, ([, level]) => {
      const shadows = {
        'sm': '0 1px 2px 0 var(--brand-shadow)',
        'md': '0 4px 6px -1px var(--brand-shadow)',
        'lg': '0 10px 15px -3px var(--brand-shadow)',
        'xl': '0 20px 25px -5px var(--brand-shadow)',
      }
      return { 'box-shadow': shadows[level] }
    }],
    
    // 动画规则
    [/^animate-(.+)$/, ([, name]) => {
      const animations = {
        'fade-in': 'fadeIn 0.5s ease-in-out',
        'slide-up': 'slideUp 0.3s ease-out',
        'slide-down': 'slideDown 0.3s ease-out',
        'scale-in': 'scaleIn 0.2s ease-out',
        'bounce-in': 'bounceIn 0.6s ease-out',
      }
      return { animation: animations[name] }
    }],
  ],
  
  // 自定义变体
  variants: [
    // 响应式变体
    (matcher) => {
      const breakpoints = {
        'xs:': '475px',
        'sm:': '640px',
        'md:': '768px',
        'lg:': '1024px',
        'xl:': '1280px',
        '2xl:': '1536px',
      }
      
      for (const [prefix, size] of Object.entries(breakpoints)) {
        if (matcher.startsWith(prefix)) {
          return {
            matcher: matcher.slice(prefix.length),
            parent: `@media (min-width: ${size})`,
          }
        }
      }
      return matcher
    },
    
    // 伪类变体
    (matcher) => {
      const pseudoClasses = {
        'hover:': ':hover',
        'focus:': ':focus',
        'active:': ':active',
        'disabled:': ':disabled',
        'group-hover:': '.group:hover &',
        'group-focus:': '.group:focus &',
      }
      
      for (const [prefix, pseudo] of Object.entries(pseudoClasses)) {
        if (matcher.startsWith(prefix)) {
          return {
            matcher: matcher.slice(prefix.length),
            selector: s => pseudo.includes('&') ? pseudo.replace('&', s) : `${s}${pseudo}`,
          }
        }
      }
      return matcher
    },
    
    // 暗色模式变体
    (matcher) => {
      if (!matcher.startsWith('dark:')) return matcher
      return {
        matcher: matcher.slice(5),
        selector: s => `.dark ${s}`,
      }
    },
    
    // 自定义状态变体
    (matcher) => {
      const states = {
        'loading:': '[data-loading="true"]',
        'error:': '[data-error="true"]',
        'success:': '[data-success="true"]',
        'open:': '[data-open="true"]',
        'closed:': '[data-open="false"]',
      }
      
      for (const [prefix, selector] of Object.entries(states)) {
        if (matcher.startsWith(prefix)) {
          return {
            matcher: matcher.slice(prefix.length),
            selector: s => `${s}${selector}`,
          }
        }
      }
      return matcher
    },
    
    // 打印变体
    (matcher) => {
      if (!matcher.startsWith('print:')) return matcher
      return {
        matcher: matcher.slice(6),
        parent: '@media print',
      }
    },
  ],
  
  // 快捷方式
  shortcuts: {
    // 按钮组件
    'btn': 'inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
    'btn-primary': 'btn bg-brand-600 text-white hover:bg-brand-700 focus:ring-brand-500',
    'btn-secondary': 'btn bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500',
    'btn-outline': 'btn border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 focus:ring-brand-500',
    'btn-ghost': 'btn text-gray-700 hover:bg-gray-100 focus:ring-gray-500',
    'btn-danger': 'btn bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
    
    // 按钮尺寸
    'btn-xs': 'px-2 py-1 text-xs',
    'btn-sm': 'px-3 py-1.5 text-sm',
    'btn-lg': 'px-6 py-3 text-base',
    'btn-xl': 'px-8 py-4 text-lg',
    
    // 输入框组件
    'input': 'block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-brand-500 focus:border-brand-500',
    'input-error': 'input border-red-300 focus:ring-red-500 focus:border-red-500',
    'input-success': 'input border-green-300 focus:ring-green-500 focus:border-green-500',
    
    // 卡片组件
    'card': 'bg-white rounded-lg shadow border border-gray-200',
    'card-hover': 'card hover:shadow-md transition-shadow',
    'card-body': 'p-6',
    'card-header': 'px-6 py-4 border-b border-gray-200',
    'card-footer': 'px-6 py-4 border-t border-gray-200',
    
    // 布局组件
    'container': 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8',
    'section': 'py-12 sm:py-16 lg:py-20',
    'flex-center': 'flex items-center justify-center',
    'flex-between': 'flex items-center justify-between',
    'grid-auto': 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6',
    
    // 文本组件
    'heading-1': 'text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight',
    'heading-2': 'text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight',
    'heading-3': 'text-2xl sm:text-3xl lg:text-4xl font-bold tracking-tight',
    'body-large': 'text-lg sm:text-xl text-gray-600',
    'body': 'text-base text-gray-600',
    'body-small': 'text-sm text-gray-500',
    
    // 状态组件
    'loading': 'opacity-50 pointer-events-none',
    'disabled': 'opacity-50 cursor-not-allowed',
    'hidden': 'sr-only',
    'visible': 'not-sr-only',
  },
  
  // 主题配置
  theme: {
    colors: {
      brand: {
        50: '#eff6ff',
        100: '#dbeafe',
        200: '#bfdbfe',
        300: '#93c5fd',
        400: '#60a5fa',
        500: '#3b82f6',
        600: '#2563eb',
        700: '#1d4ed8',
        800: '#1e40af',
        900: '#1e3a8a',
      },
    },
    fontFamily: {
      sans: ['Inter', 'system-ui', 'sans-serif'],
      mono: ['JetBrains Mono', 'monospace'],
    },
    animation: {
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        slideUp: {
          '0%': { transform: 'translateY(10px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' },
        },
        slideDown: {
          '0%': { transform: 'translateY(-10px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' },
        },
        scaleIn: {
          '0%': { transform: 'scale(0.95)', opacity: '0' },
          '100%': { transform: 'scale(1)', opacity: '1' },
        },
        bounceIn: {
          '0%': { transform: 'scale(0.3)', opacity: '0' },
          '50%': { transform: 'scale(1.05)' },
          '70%': { transform: 'scale(0.9)' },
          '100%': { transform: 'scale(1)', opacity: '1' },
        },
      },
    },
  },
})

实际应用案例

设计系统实现

<!-- 使用自定义规则和快捷方式的组件 -->

<!-- 按钮组件 -->
<div class="space-y-4">
  <button class="btn-primary btn-lg">
    主要按钮
  </button>
  
  <button class="btn-secondary btn-md">
    次要按钮
  </button>
  
  <button class="btn-outline btn-sm">
    轮廓按钮
  </button>
  
  <button class="btn-ghost btn-xs">
    幽灵按钮
  </button>
</div>

<!-- 表单组件 -->
<form class="space-y-6">
  <div>
    <label class="block text-sm font-medium text-gray-700 mb-2">
      邮箱地址
    </label>
    <input type="email" class="input" placeholder="请输入邮箱">
  </div>
  
  <div>
    <label class="block text-sm font-medium text-gray-700 mb-2">
      密码
    </label>
    <input type="password" class="input-error" placeholder="请输入密码">
    <p class="mt-1 text-sm text-red-600">密码长度至少8位</p>
  </div>
  
  <button type="submit" class="btn-primary w-full">
    登录
  </button>
</form>

<!-- 卡片组件 -->
<div class="grid-auto">
  <div class="card-hover">
    <div class="card-header">
      <h3 class="heading-3">产品标题</h3>
    </div>
    <div class="card-body">
      <p class="body">产品描述内容...</p>
    </div>
    <div class="card-footer">
      <button class="btn-primary btn-sm">
        了解更多
      </button>
    </div>
  </div>
</div>

<!-- 状态示例 -->
<div class="space-y-4">
  <!-- 加载状态 -->
  <button class="btn-primary loading" data-loading="true">
    加载中...
  </button>
  
  <!-- 错误状态 -->
  <div class="card error:border-red-500" data-error="true">
    <p class="text-red-600">发生错误</p>
  </div>
  
  <!-- 成功状态 -->
  <div class="card success:border-green-500" data-success="true">
    <p class="text-green-600">操作成功</p>
  </div>
</div>

<!-- 响应式组件 -->
<div class="container section">
  <h1 class="heading-1 text-center mb-8">
    响应式标题
  </h1>
  
  <p class="body-large text-center max-w-3xl mx-auto mb-12">
    这是一个响应式的描述文本,在不同屏幕尺寸下有不同的显示效果。
  </p>
  
  <div class="grid-auto">
    <div class="card-hover">
      <h3 class="heading-3 mb-4">特性 1</h3>
      <p class="body">特性描述...</p>
    </div>
    
    <div class="card-hover">
      <h3 class="heading-3 mb-4">特性 2</h3>
      <p class="body">特性描述...</p>
    </div>
    
    <div class="card-hover">
      <h3 class="heading-3 mb-4">特性 3</h3>
      <p class="body">特性描述...</p>
    </div>
  </div>
</div>

<!-- 暗色模式支持 -->
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
  <div class="container section">
    <h2 class="heading-2 mb-8">暗色模式示例</h2>
    
    <div class="card dark:bg-gray-800 dark:border-gray-700">
      <div class="card-body">
        <p class="body dark:text-gray-300">
          这个卡片在暗色模式下有不同的样式。
        </p>
        
        <button class="btn-primary mt-4">
          按钮在暗色模式下也会自动适配
        </button>
      </div>
    </div>
  </div>
</div>

动态主题切换

// 主题切换功能
class ThemeManager {
  constructor() {
    this.theme = localStorage.getItem('theme') || 'light'
    this.applyTheme()
  }
  
  applyTheme() {
    const root = document.documentElement
    
    if (this.theme === 'dark') {
      root.classList.add('dark')
    } else {
      root.classList.remove('dark')
    }
    
    // 更新CSS变量
    const colors = this.theme === 'dark' ? {
      '--brand-shadow': 'rgba(0, 0, 0, 0.3)',
      '--text-primary': '#ffffff',
      '--bg-primary': '#1f2937',
    } : {
      '--brand-shadow': 'rgba(0, 0, 0, 0.1)',
      '--text-primary': '#111827',
      '--bg-primary': '#ffffff',
    }
    
    Object.entries(colors).forEach(([property, value]) => {
      root.style.setProperty(property, value)
    })
  }
  
  toggle() {
    this.theme = this.theme === 'light' ? 'dark' : 'light'
    localStorage.setItem('theme', this.theme)
    this.applyTheme()
  }
}

// 使用示例
const themeManager = new ThemeManager()

// 主题切换按钮
document.getElementById('theme-toggle').addEventListener('click', () => {
  themeManager.toggle()
})

性能优化技巧

1. 规则优化

// 优化规则性能
const optimizedRules = [
  // 使用缓存避免重复计算
  [/^m-(.+)$/, ([, d], { cache }) => {
    const key = `margin-${d}`
    if (cache.has(key)) return cache.get(key)
    
    const result = { margin: `${d}px` }
    cache.set(key, result)
    return result
  }],
  
  // 预编译常用值
  ...['1', '2', '4', '8', '16', '32'].map(size => [
    `m-${size}`,
    { margin: `${size}px` }
  ]),
]

2. 变体优化

// 优化变体性能
const optimizedVariants = [
  // 使用Map提高查找性能
  (() => {
    const pseudoMap = new Map([
      ['hover:', ':hover'],
      ['focus:', ':focus'],
      ['active:', ':active'],
    ])
    
    return (matcher) => {
      for (const [prefix, pseudo] of pseudoMap) {
        if (matcher.startsWith(prefix)) {
          return {
            matcher: matcher.slice(prefix.length),
            selector: s => `${s}${pseudo}`,
          }
        }
      }
      return matcher
    }
  })(),
]

3. 快捷方式优化

// 优化快捷方式
const optimizedShortcuts = {
  // 预编译常用组合
  'btn-primary': 'btn bg-blue-500 text-white hover:bg-blue-600',
  
  // 使用函数避免字符串拼接
  'btn-size': (size) => {
    const sizes = {
      sm: 'px-3 py-1.5 text-sm',
      md: 'px-4 py-2 text-base',
      lg: 'px-6 py-3 text-lg',
    }
    return `btn ${sizes[size] || sizes.md}`
  },
}

调试和测试

1. 规则调试

// 调试工具
const debugRules = [
  [/^debug-(.+)$/, ([, className]) => {
    console.log(`Debug: ${className}`)
    return {
      'outline': '2px solid red',
      'outline-offset': '2px',
      'position': 'relative',
    }
  }],
]

// 使用方式
// <div class="debug-container">调试容器</div>

2. 性能测试

// 性能测试工具
function benchmarkRule(rule, testCases) {
  const start = performance.now()
  
  testCases.forEach(testCase => {
    rule[1](testCase.match(rule[0]))
  })
  
  const end = performance.now()
  console.log(`Rule performance: ${end - start}ms`)
}

// 测试用例
const testCases = [
  'm-1', 'm-2', 'm-4', 'm-8', 'm-16',
  'p-1', 'p-2', 'p-4', 'p-8', 'p-16',
]

benchmarkRule([/^[mp]-(.+)$/, ([, d]) => ({ margin: `${d}px` })], testCases)

本章总结

通过本章学习,我们深入掌握了:

  1. 规则系统:静态规则、动态规则、函数式规则的创建和使用
  2. 变体系统:伪类、伪元素、响应式、自定义变体的实现
  3. 快捷方式:组件样式的封装和复用
  4. 企业级配置:完整的设计系统实现
  5. 性能优化:规则、变体、快捷方式的优化技巧
  6. 调试测试:开发过程中的调试和性能测试方法

核心要点

  • 自定义规则让UnoCSS具有无限的扩展性
  • 变体系统提供了强大的条件样式应用能力
  • 快捷方式是构建设计系统的重要工具
  • 性能优化对于大型项目至关重要

下一步

在下一章中,我们将学习UnoCSS的高级特性,包括属性化模式、图标系统等。

练习题

  1. 自定义按钮系统:创建一套完整的按钮组件规则和快捷方式
  2. 响应式网格:实现一个自定义的响应式网格系统
  3. 主题切换:创建支持多主题的变体系统
  4. 动画库:实现一套自定义动画规则
  5. 性能测试:对比自定义规则与内置规则的性能差异