本章概述

本章将探讨 UnoCSS 的高级特性,包括属性化模式、图标系统、Web字体集成、排版预设等,以及如何开发和使用插件来扩展 UnoCSS 的功能。

属性化模式 (Attributify Mode)

什么是属性化模式

属性化模式允许你将工具类写在HTML属性中,而不是class属性中,这样可以提高代码的可读性和组织性。

<!-- 传统方式 -->
<div class="bg-blue-500 text-white p-4 rounded-lg shadow-md hover:bg-blue-600 transition-colors">
  传统方式
</div>

<!-- 属性化模式 -->
<div 
  bg="blue-500 hover:blue-600" 
  text="white" 
  p="4" 
  rounded="lg" 
  shadow="md" 
  transition="colors"
>
  属性化模式
</div>

启用属性化模式

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

export default defineConfig({
  presets: [
    presetUno(),
    presetAttributify({
      // 配置选项
      prefix: 'un-',           // 属性前缀
      prefixedOnly: false,     // 是否只使用带前缀的属性
      nonValuedAttribute: true, // 支持无值属性
      ignoreAttributes: ['class', 'style'], // 忽略的属性
    }),
  ],
})

属性化语法详解

1. 基础语法

<!-- 颜色属性 -->
<div bg="red-500" text="white">红色背景,白色文字</div>
<div bg="gradient-to-r from-blue-500 to-purple-600">渐变背景</div>

<!-- 尺寸属性 -->
<div w="64" h="32">固定尺寸</div>
<div w="full" h="screen">全宽全高</div>
<div w="1/2" h="auto">响应式尺寸</div>

<!-- 间距属性 -->
<div p="4" m="2">内外边距</div>
<div px="6" py="3">水平垂直边距</div>
<div pt="2" pb="4" pl="3" pr="5">单独设置</div>

<!-- 布局属性 -->
<div flex="~ col items-center justify-between">弹性布局</div>
<div grid="~ cols-3 gap-4">网格布局</div>
<div absolute="~ top-0 left-0">绝对定位</div>

2. 响应式属性

<!-- 响应式属性 -->
<div 
  w="full sm:1/2 md:1/3 lg:1/4"
  p="2 sm:4 md:6 lg:8"
  text="sm sm:base md:lg lg:xl"
>
  响应式属性
</div>

<!-- 复杂响应式布局 -->
<div 
  flex="~ col sm:row"
  items="center sm:start"
  justify="center sm:between"
  gap="4 sm:6 md:8"
>
  <div flex="1">内容1</div>
  <div flex="1">内容2</div>
</div>

3. 伪类属性

<!-- 交互状态 -->
<button 
  bg="blue-500 hover:blue-600 active:blue-700"
  text="white"
  p="2 4"
  rounded="md"
  transition="colors"
>
  交互按钮
</button>

<!-- 焦点状态 -->
<input 
  border="2 gray-300 focus:blue-500"
  rounded="md"
  p="2"
  outline="none"
  ring="0 focus:2 focus:blue-500"
>

<!-- 组合状态 -->
<div 
  group
  bg="white hover:gray-50"
  border="1 gray-200"
  rounded="lg"
  p="4"
  cursor="pointer"
>
  <h3 text="lg font-semibold group-hover:text-blue-600">标题</h3>
  <p text="gray-600 group-hover:text-gray-800">描述</p>
</div>

4. 无值属性

<!-- 无值属性 -->
<div flex items-center justify-center>居中布局</div>
<div hidden>隐藏元素</div>
<div relative>相对定位</div>
<div absolute>绝对定位</div>
<div fixed>固定定位</div>

<!-- 带前缀的无值属性 -->
<div un-flex un-items-center un-justify-center>带前缀</div>

属性化模式最佳实践

1. 组织属性

<!-- 推荐:按功能分组 -->
<div 
  <!-- 布局 -->
  flex="~ col items-center justify-center"
  <!-- 尺寸 -->
  w="full max-w-md"
  h="auto min-h-64"
  <!-- 间距 -->
  p="6"
  m="4"
  <!-- 外观 -->
  bg="white"
  border="1 gray-200"
  rounded="lg"
  shadow="md"
  <!-- 交互 -->
  hover="shadow-lg"
  transition="shadow"
>
  内容
</div>

<!-- 避免:混乱的属性顺序 -->
<div bg="white" flex p="6" w="full" rounded="lg" items-center>
  内容
</div>

2. 复杂布局示例

<!-- 卡片组件 -->
<article 
  bg="white dark:gray-800"
  border="1 gray-200 dark:gray-700"
  rounded="xl"
  shadow="sm hover:md"
  transition="shadow"
  overflow="hidden"
>
  <!-- 图片区域 -->
  <div 
    w="full"
    h="48"
    bg="gray-200"
    overflow="hidden"
  >
    <img 
      w="full"
      h="full"
      object="cover"
      src="image.jpg"
      alt="图片"
    >
  </div>
  
  <!-- 内容区域 -->
  <div p="6">
    <h3 
      text="xl font-semibold gray-900 dark:white"
      mb="2"
    >
      文章标题
    </h3>
    
    <p 
      text="gray-600 dark:gray-300"
      leading="relaxed"
      mb="4"
    >
      文章摘要内容...
    </p>
    
    <div 
      flex="~ items-center justify-between"
    >
      <span 
        text="sm gray-500 dark:gray-400"
      >
        2024年1月15日
      </span>
      
      <button 
        bg="blue-500 hover:blue-600"
        text="white"
        px="4"
        py="2"
        rounded="md"
        text="sm font-medium"
        transition="colors"
      >
        阅读更多
      </button>
    </div>
  </div>
</article>

<!-- 表单组件 -->
<form 
  space="y-6"
  max-w="md"
  mx="auto"
  p="6"
  bg="white"
  rounded="lg"
  shadow="md"
>
  <div>
    <label 
      block
      text="sm font-medium gray-700"
      mb="2"
    >
      邮箱地址
    </label>
    <input 
      type="email"
      w="full"
      px="3"
      py="2"
      border="1 gray-300 focus:blue-500"
      rounded="md"
      outline="none"
      ring="0 focus:2 focus:blue-500"
      placeholder="请输入邮箱"
    >
  </div>
  
  <div>
    <label 
      block
      text="sm font-medium gray-700"
      mb="2"
    >
      密码
    </label>
    <input 
      type="password"
      w="full"
      px="3"
      py="2"
      border="1 gray-300 focus:blue-500"
      rounded="md"
      outline="none"
      ring="0 focus:2 focus:blue-500"
      placeholder="请输入密码"
    >
  </div>
  
  <button 
    type="submit"
    w="full"
    bg="blue-500 hover:blue-600"
    text="white"
    py="2"
    px="4"
    rounded="md"
    font="medium"
    transition="colors"
  >
    登录
  </button>
</form>

图标系统

图标预设介绍

UnoCSS 提供了强大的图标系统,支持多种图标库的集成。

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

export default defineConfig({
  presets: [
    presetUno(),
    presetIcons({
      // 图标集合
      collections: {
        // 使用 Iconify 图标
        carbon: () => import('@iconify-json/carbon/icons.json').then(i => i.default),
        mdi: () => import('@iconify-json/mdi/icons.json').then(i => i.default),
        tabler: () => import('@iconify-json/tabler/icons.json').then(i => i.default),
        
        // 自定义图标
        custom: {
          logo: '<svg>...</svg>',
          icon1: '<svg>...</svg>',
        },
      },
      
      // 配置选项
      scale: 1.2,              // 默认缩放
      warn: true,              // 警告缺失图标
      prefix: 'i-',            // 图标前缀
      extraProperties: {       // 额外CSS属性
        'display': 'inline-block',
        'vertical-align': 'middle',
      },
    }),
  ],
})

图标使用方法

1. 基础用法

<!-- 基础图标 -->
<div class="i-carbon-home"></div>
<div class="i-mdi-account"></div>
<div class="i-tabler-settings"></div>

<!-- 自定义图标 -->
<div class="i-custom-logo"></div>

<!-- 图标尺寸 -->
<div class="i-carbon-home text-lg"></div>
<div class="i-carbon-home text-2xl"></div>
<div class="i-carbon-home w-8 h-8"></div>

<!-- 图标颜色 -->
<div class="i-carbon-home text-red-500"></div>
<div class="i-carbon-home text-blue-600"></div>

2. 响应式图标

<!-- 响应式尺寸 -->
<div class="i-carbon-menu text-lg sm:text-xl md:text-2xl"></div>

<!-- 响应式显示 -->
<div class="i-carbon-menu block md:hidden"></div>
<div class="i-carbon-close hidden md:block"></div>

3. 图标组合

<!-- 带文字的图标 -->
<button class="flex items-center space-x-2">
  <div class="i-carbon-add"></div>
  <span>添加</span>
</button>

<!-- 图标按钮 -->
<button class="p-2 rounded-md hover:bg-gray-100">
  <div class="i-carbon-settings w-5 h-5"></div>
</button>

<!-- 图标列表 -->
<ul class="space-y-2">
  <li class="flex items-center space-x-3">
    <div class="i-carbon-checkmark text-green-500"></div>
    <span>已完成</span>
  </li>
  <li class="flex items-center space-x-3">
    <div class="i-carbon-time text-yellow-500"></div>
    <span>进行中</span>
  </li>
  <li class="flex items-center space-x-3">
    <div class="i-carbon-close text-red-500"></div>
    <span>已取消</span>
  </li>
</ul>

自定义图标集

1. SVG图标集

// 自定义图标集
const customIcons = {
  // 简单SVG
  'arrow-right': '<svg viewBox="0 0 24 24"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" fill="currentColor"/></svg>',
  
  // 复杂图标
  'brand-logo': `
    <svg viewBox="0 0 100 100">
      <circle cx="50" cy="50" r="40" fill="currentColor"/>
      <text x="50" y="55" text-anchor="middle" fill="white" font-size="20">LOGO</text>
    </svg>
  `,
  
  // 多色图标
  'colorful-icon': `
    <svg viewBox="0 0 24 24">
      <circle cx="12" cy="12" r="10" fill="#3b82f6"/>
      <path d="M8 12l2 2 4-4" stroke="white" stroke-width="2" fill="none"/>
    </svg>
  `,
}

// 配置
export default defineConfig({
  presets: [
    presetIcons({
      collections: {
        custom: customIcons,
      },
    }),
  ],
})

2. 动态图标加载

// 动态加载图标
const dynamicIcons = {
  async loadIcon(name) {
    try {
      const response = await fetch(`/icons/${name}.svg`)
      return await response.text()
    } catch (error) {
      console.warn(`Icon ${name} not found`)
      return null
    }
  },
}

// 使用文件系统图标
import { promises as fs } from 'fs'
import { join } from 'path'

const fileSystemIcons = {
  async loadIcon(name) {
    try {
      const iconPath = join(process.cwd(), 'assets/icons', `${name}.svg`)
      return await fs.readFile(iconPath, 'utf-8')
    } catch (error) {
      return null
    }
  },
}

Web字体集成

Web字体预设

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

export default defineConfig({
  presets: [
    presetUno(),
    presetWebFonts({
      // Google Fonts
      provider: 'google',
      fonts: {
        // 无衬线字体
        sans: 'Inter:400,500,600,700',
        serif: 'Merriweather:400,700',
        mono: 'JetBrains Mono:400,500,700',
        
        // 自定义字体名称
        heading: 'Poppins:600,700,800',
        body: 'Open Sans:400,600',
        
        // 多个字体备选
        display: [
          'Playfair Display:400,700',
          'serif',
        ],
      },
      
      // 配置选项
      extendTheme: true,       // 扩展主题
      inlineImports: true,     // 内联导入
      themeKey: 'fontFamily',  // 主题键名
    }),
  ],
})

字体使用方法

<!-- 基础字体 -->
<h1 class="font-heading text-4xl font-bold">标题字体</h1>
<p class="font-body text-base">正文字体</p>
<code class="font-mono text-sm">代码字体</code>

<!-- 字体粗细 -->
<div class="font-sans">
  <p class="font-normal">正常粗细</p>
  <p class="font-medium">中等粗细</p>
  <p class="font-semibold">半粗体</p>
  <p class="font-bold">粗体</p>
</div>

<!-- 响应式字体 -->
<h1 class="font-heading text-2xl sm:text-3xl md:text-4xl lg:text-5xl">
  响应式标题
</h1>

<!-- 字体样式组合 -->
<article class="font-body">
  <h1 class="font-heading font-bold text-3xl mb-4">文章标题</h1>
  <p class="text-lg leading-relaxed text-gray-700 mb-6">
    文章内容使用易读的正文字体...
  </p>
  <code class="font-mono bg-gray-100 px-2 py-1 rounded text-sm">
    代码片段
  </code>
</article>

自定义字体提供商

// 自定义字体提供商
const customFontProvider = {
  name: 'custom',
  async getFontURL(font) {
    // 自定义字体URL生成逻辑
    return `https://fonts.example.com/${font.name}/${font.weights.join(',')}.css`
  },
}

export default defineConfig({
  presets: [
    presetWebFonts({
      provider: customFontProvider,
      fonts: {
        custom: 'CustomFont:400,700',
      },
    }),
  ],
})

排版预设

排版预设配置

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

export default defineConfig({
  presets: [
    presetUno(),
    presetTypography({
      // 配置选项
      cssExtend: {
        // 自定义排版样式
        'h1': {
          'font-size': '2.5rem',
          'line-height': '1.2',
          'margin-bottom': '1rem',
        },
        'p': {
          'margin-bottom': '1rem',
          'line-height': '1.6',
        },
        'code': {
          'background-color': '#f3f4f6',
          'padding': '0.125rem 0.25rem',
          'border-radius': '0.25rem',
          'font-size': '0.875rem',
        },
      },
    }),
  ],
})

排版类使用

<!-- 基础排版 -->
<article class="prose">
  <h1>文章标题</h1>
  <p>这是一个段落,包含了基础的排版样式。</p>
  <h2>二级标题</h2>
  <p>另一个段落,展示了排版预设的效果。</p>
  
  <ul>
    <li>列表项 1</li>
    <li>列表项 2</li>
    <li>列表项 3</li>
  </ul>
  
  <blockquote>
    这是一个引用块,展示了引用的样式。
  </blockquote>
  
  <pre><code>const code = 'example';</code></pre>
</article>

<!-- 不同尺寸的排版 -->
<article class="prose prose-sm">小尺寸排版</article>
<article class="prose prose-lg">大尺寸排版</article>
<article class="prose prose-xl">超大尺寸排版</article>

<!-- 暗色模式排版 -->
<article class="prose dark:prose-invert">
  <h1>暗色模式标题</h1>
  <p>暗色模式下的段落文本。</p>
</article>

<!-- 自定义颜色排版 -->
<article class="prose prose-blue">
  <h1>蓝色主题排版</h1>
  <p>使用蓝色主题的排版样式。</p>
</article>

插件系统

插件基础概念

插件是扩展 UnoCSS 功能的模块,可以添加新的预设、规则、变体等。

// 基础插件结构
function myPlugin(options = {}) {
  return {
    name: 'my-plugin',
    
    // 添加规则
    rules: [
      ['my-rule', { color: 'red' }],
    ],
    
    // 添加变体
    variants: [
      (matcher) => {
        if (matcher.startsWith('my:')) {
          return {
            matcher: matcher.slice(3),
            selector: s => `.my-context ${s}`,
          }
        }
        return matcher
      },
    ],
    
    // 添加快捷方式
    shortcuts: {
      'my-btn': 'px-4 py-2 bg-blue-500 text-white rounded',
    },
    
    // 主题扩展
    theme: {
      colors: {
        primary: '#3b82f6',
      },
    },
    
    // 预处理器
    preprocess(matcher) {
      // 预处理逻辑
      return matcher
    },
    
    // 后处理器
    postprocess(util) {
      // 后处理逻辑
      return util
    },
  }
}

// 使用插件
export default defineConfig({
  plugins: [
    myPlugin({
      // 插件选项
    }),
  ],
})

实用插件开发

1. 动画插件

// 动画插件
function animationPlugin(options = {}) {
  const animations = {
    'fade-in': {
      'animation': 'fadeIn 0.5s ease-in-out',
      '@keyframes fadeIn': {
        '0%': { opacity: '0' },
        '100%': { opacity: '1' },
      },
    },
    'slide-up': {
      'animation': 'slideUp 0.3s ease-out',
      '@keyframes slideUp': {
        '0%': { transform: 'translateY(10px)', opacity: '0' },
        '100%': { transform: 'translateY(0)', opacity: '1' },
      },
    },
    'bounce-in': {
      'animation': 'bounceIn 0.6s ease-out',
      '@keyframes bounceIn': {
        '0%': { transform: 'scale(0.3)', opacity: '0' },
        '50%': { transform: 'scale(1.05)' },
        '70%': { transform: 'scale(0.9)' },
        '100%': { transform: 'scale(1)', opacity: '1' },
      },
    },
  }
  
  return {
    name: 'animation-plugin',
    rules: [
      [/^animate-(.+)$/, ([, name]) => {
        return animations[name] || {}
      }],
    ],
  }
}

// 使用方式
// <div class="animate-fade-in">淡入动画</div>
// <div class="animate-slide-up">滑入动画</div>

2. 主题插件

// 主题插件
function themePlugin(themes = {}) {
  return {
    name: 'theme-plugin',
    
    variants: [
      (matcher) => {
        for (const themeName of Object.keys(themes)) {
          if (matcher.startsWith(`${themeName}:`)) {
            return {
              matcher: matcher.slice(themeName.length + 1),
              selector: s => `[data-theme="${themeName}"] ${s}`,
            }
          }
        }
        return matcher
      },
    ],
    
    rules: [
      // 主题颜色规则
      [/^theme-(.+)$/, ([, colorName]) => {
        return { color: `var(--theme-${colorName})` }
      }],
      [/^bg-theme-(.+)$/, ([, colorName]) => {
        return { 'background-color': `var(--theme-${colorName})` }
      }],
    ],
    
    // 生成CSS变量
    preflights: [
      {
        getCSS() {
          let css = ''
          for (const [themeName, colors] of Object.entries(themes)) {
            css += `[data-theme="${themeName}"] {\n`
            for (const [colorName, colorValue] of Object.entries(colors)) {
              css += `  --theme-${colorName}: ${colorValue};\n`
            }
            css += '}\n'
          }
          return css
        },
      },
    ],
  }
}

// 使用示例
const themes = {
  light: {
    primary: '#3b82f6',
    secondary: '#6b7280',
    background: '#ffffff',
    text: '#111827',
  },
  dark: {
    primary: '#60a5fa',
    secondary: '#9ca3af',
    background: '#111827',
    text: '#f9fafb',
  },
}

export default defineConfig({
  plugins: [
    themePlugin(themes),
  ],
})

// HTML使用
// <div data-theme="light">
//   <p class="theme-text bg-theme-background">浅色主题</p>
// </div>
// <div data-theme="dark">
//   <p class="theme-text bg-theme-background">深色主题</p>
// </div>

3. 组件插件

// 组件插件
function componentPlugin(components = {}) {
  return {
    name: 'component-plugin',
    
    shortcuts: Object.entries(components).reduce((acc, [name, styles]) => {
      acc[name] = styles
      return acc
    }, {}),
    
    rules: [
      // 组件变体
      [/^(.+)-variant-(.+)$/, ([, component, variant]) => {
        const componentConfig = components[component]
        if (componentConfig && componentConfig.variants && componentConfig.variants[variant]) {
          return componentConfig.variants[variant]
        }
        return {}
      }],
    ],
  }
}

// 组件定义
const components = {
  'btn': 'inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md transition-colors',
  'card': 'bg-white rounded-lg shadow border border-gray-200 p-6',
  'input': 'block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2',
  
  // 带变体的组件
  'alert': {
    base: 'p-4 rounded-md border',
    variants: {
      info: { 'background-color': '#dbeafe', 'border-color': '#3b82f6', color: '#1e40af' },
      success: { 'background-color': '#dcfce7', 'border-color': '#22c55e', color: '#166534' },
      warning: { 'background-color': '#fef3c7', 'border-color': '#f59e0b', color: '#92400e' },
      error: { 'background-color': '#fee2e2', 'border-color': '#ef4444', color: '#991b1b' },
    },
  },
}

export default defineConfig({
  plugins: [
    componentPlugin(components),
  ],
})

// 使用方式
// <button class="btn">基础按钮</button>
// <div class="card">卡片内容</div>
// <div class="alert alert-variant-success">成功提示</div>

插件生态系统

1. 官方插件

// 常用官方插件
import {
  defineConfig,
  presetUno,           // 基础预设
  presetAttributify,   // 属性化模式
  presetIcons,         // 图标系统
  presetWebFonts,      // Web字体
  presetTypography,    // 排版
  presetWind,          // Tailwind CSS 兼容
  presetMini,          // 最小预设
  transformerDirectives,     // 指令转换器
  transformerVariantGroup,   // 变体组转换器
  transformerCompileClass,   // 编译类转换器
} from 'unocss'

export default defineConfig({
  presets: [
    presetUno(),
    presetAttributify(),
    presetIcons(),
    presetWebFonts({
      fonts: {
        sans: 'Inter',
        mono: 'JetBrains Mono',
      },
    }),
    presetTypography(),
  ],
  
  transformers: [
    transformerDirectives(),
    transformerVariantGroup(),
    transformerCompileClass(),
  ],
})

2. 社区插件

// 社区插件示例
import { defineConfig } from 'unocss'
import { presetScrollbar } from 'unocss-preset-scrollbar'
import { presetAnimations } from 'unocss-preset-animations'
import { presetForms } from 'unocss-preset-forms'

export default defineConfig({
  presets: [
    // 滚动条样式
    presetScrollbar(),
    
    // 动画预设
    presetAnimations(),
    
    // 表单样式
    presetForms(),
  ],
})

转换器系统

指令转换器

/* CSS指令 */
.custom {
  @apply flex items-center justify-center;
  @apply bg-blue-500 text-white;
  @apply hover:bg-blue-600 transition-colors;
}

/* 编译后 */
.custom {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #3b82f6;
  color: #ffffff;
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 150ms;
}
.custom:hover {
  background-color: #2563eb;
}

变体组转换器

<!-- 变体组语法 -->
<div class="hover:(bg-blue-500 text-white) focus:(ring-2 ring-blue-500)">
  变体组
</div>

<!-- 编译后 -->
<div class="hover:bg-blue-500 hover:text-white focus:ring-2 focus:ring-blue-500">
  变体组
</div>

<!-- 复杂变体组 -->
<div class="sm:(text-lg font-semibold) md:(text-xl font-bold) lg:(text-2xl font-extrabold)">
  响应式变体组
</div>

编译类转换器

<!-- 编译类 -->
<div class="uno: flex items-center justify-center bg-blue-500 text-white p-4 rounded-lg">
  编译类
</div>

<!-- 编译后生成唯一类名 -->
<div class="uno-abc123">
  编译类
</div>

<style>
.uno-abc123 {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #3b82f6;
  color: #ffffff;
  padding: 1rem;
  border-radius: 0.5rem;
}
</style>

本章总结

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

  1. 属性化模式:提高HTML可读性的新写法
  2. 图标系统:集成多种图标库的强大功能
  3. Web字体:简化字体集成和管理
  4. 排版预设:专业的文档排版样式
  5. 插件系统:扩展UnoCSS功能的核心机制
  6. 转换器:增强开发体验的工具

核心要点

  • 属性化模式让HTML更加语义化和易读
  • 图标系统提供了统一的图标管理方案
  • 插件系统是UnoCSS生态的核心
  • 转换器提供了更好的开发体验

下一步

在下一章中,我们将学习UnoCSS的性能优化技巧和最佳实践。

练习题

  1. 属性化重构:将传统class写法转换为属性化模式
  2. 自定义图标库:创建项目专用的图标集合
  3. 主题插件:开发支持多主题切换的插件
  4. 组件系统:使用插件创建完整的组件库
  5. 性能测试:对比不同特性的性能影响