本章概述
本章将深入探讨 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)
本章总结
通过本章学习,我们深入掌握了:
- 规则系统:静态规则、动态规则、函数式规则的创建和使用
- 变体系统:伪类、伪元素、响应式、自定义变体的实现
- 快捷方式:组件样式的封装和复用
- 企业级配置:完整的设计系统实现
- 性能优化:规则、变体、快捷方式的优化技巧
- 调试测试:开发过程中的调试和性能测试方法
核心要点
- 自定义规则让UnoCSS具有无限的扩展性
- 变体系统提供了强大的条件样式应用能力
- 快捷方式是构建设计系统的重要工具
- 性能优化对于大型项目至关重要
下一步
在下一章中,我们将学习UnoCSS的高级特性,包括属性化模式、图标系统等。
练习题
- 自定义按钮系统:创建一套完整的按钮组件规则和快捷方式
- 响应式网格:实现一个自定义的响应式网格系统
- 主题切换:创建支持多主题的变体系统
- 动画库:实现一套自定义动画规则
- 性能测试:对比自定义规则与内置规则的性能差异