10.1 性能优化概述

10.1.1 性能指标

在 Tailwind CSS 项目中,我们需要关注以下性能指标:

  • CSS 文件大小:最终生成的 CSS 文件大小
  • 构建时间:从源码到生产文件的编译时间
  • 运行时性能:浏览器渲染和交互性能
  • 首次内容绘制 (FCP):页面首次渲染内容的时间
  • 最大内容绘制 (LCP):页面主要内容完成渲染的时间
  • 累积布局偏移 (CLS):页面布局稳定性指标

10.1.2 优化策略

  1. 减少 CSS 文件大小
  2. 优化构建流程
  3. 提升运行时性能
  4. 改善用户体验
  5. 监控和分析

10.2 CSS 文件大小优化

10.2.1 内容配置优化

// tailwind.config.js - 精确的内容配置
module.exports = {
  content: [
    // 只包含实际使用的文件
    './src/**/*.{html,js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    './pages/**/*.{js,ts,jsx,tsx}',
    
    // 避免包含不必要的文件
    // './node_modules/**/*.js', // 避免这样做
    
    // 使用更精确的模式
    './src/components/**/*.vue',
    './src/pages/**/*.vue',
  ],
  
  // 安全列表 - 只添加确实需要的类
  safelist: [
    // 动态生成的类名
    {
      pattern: /bg-(red|green|blue)-(100|500|900)/,
      variants: ['hover', 'focus'],
    },
    // 第三方库需要的类
    'prose',
    'prose-lg',
  ],
  
  // 阻止列表 - 移除不需要的类
  blocklist: [
    'container', // 如果不使用容器类
    'debug-screens', // 调试相关的类
  ],
}

10.2.2 按需加载策略

/* 基础样式文件 - base.css */
@tailwind base;

/* 组件样式文件 - components.css */
@tailwind components;

/* 工具类文件 - utilities.css */
@tailwind utilities;

/* 分离关键 CSS */
/* critical.css - 首屏必需的样式 */
@layer base {
  body {
    @apply font-sans text-gray-900 bg-white;
  }
}

@layer components {
  .btn {
    @apply px-4 py-2 rounded font-medium transition-colors;
  }
  
  .btn-primary {
    @apply bg-blue-500 text-white hover:bg-blue-600;
  }
}

/* non-critical.css - 非关键样式 */
@layer utilities {
  .text-shadow {
    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
}

10.2.3 CSS 压缩和优化

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    
    // 生产环境优化
    ...(process.env.NODE_ENV === 'production' && {
      // CSS 压缩
      cssnano: {
        preset: ['default', {
          discardComments: {
            removeAll: true,
          },
          normalizeWhitespace: true,
          colormin: true,
          convertValues: true,
          discardDuplicates: true,
          discardEmpty: true,
          mergeRules: true,
          minifyFontValues: true,
          minifyParams: true,
          minifySelectors: true,
          normalizeCharset: true,
          normalizeDisplayValues: true,
          normalizePositions: true,
          normalizeRepeatStyle: true,
          normalizeString: true,
          normalizeTimingFunctions: true,
          normalizeUnicode: true,
          normalizeUrl: true,
          orderedValues: true,
          reduceIdents: true,
          reduceInitial: true,
          reduceTransforms: true,
          svgo: true,
          uniqueSelectors: true,
        }],
      },
      
      // 移除未使用的 CSS
      '@fullhuman/postcss-purgecss': {
        content: [
          './src/**/*.html',
          './src/**/*.js',
          './src/**/*.jsx',
          './src/**/*.ts',
          './src/**/*.tsx',
        ],
        defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || [],
        safelist: {
          standard: ['html', 'body'],
          deep: [/^prose/, /^hljs/],
          greedy: [/^bg-/, /^text-/],
        },
      },
    }),
  },
}

10.2.4 文件大小分析

// analyze-css.js - CSS 文件分析脚本
const fs = require('fs')
const path = require('path')
const gzipSize = require('gzip-size')
const brotliSize = require('brotli-size')

async function analyzeCSSFile(filePath) {
  const content = fs.readFileSync(filePath, 'utf8')
  const stats = fs.statSync(filePath)
  
  const originalSize = stats.size
  const gzipped = await gzipSize(content)
  const brotli = await brotliSize(content)
  
  console.log(`\n📊 CSS 文件分析: ${path.basename(filePath)}`)
  console.log(`原始大小: ${(originalSize / 1024).toFixed(2)} KB`)
  console.log(`Gzip 压缩: ${(gzipped / 1024).toFixed(2)} KB (${((1 - gzipped / originalSize) * 100).toFixed(1)}% 减少)`)
  console.log(`Brotli 压缩: ${(brotli / 1024).toFixed(2)} KB (${((1 - brotli / originalSize) * 100).toFixed(1)}% 减少)`)
  
  // 性能建议
  if (originalSize > 100 * 1024) {
    console.log('⚠️  警告: CSS 文件过大,建议进行优化')
  }
  
  if (gzipped > 50 * 1024) {
    console.log('💡 建议: 考虑代码分割或按需加载')
  }
  
  return {
    original: originalSize,
    gzipped,
    brotli,
  }
}

// 使用示例
analyzeCSSFile('./dist/styles.css')

10.3 构建性能优化

10.3.1 JIT 模式优化

// tailwind.config.js - JIT 模式配置
module.exports = {
  mode: 'jit', // 启用 JIT 模式(Tailwind CSS 3.0+ 默认启用)
  
  content: [
    './src/**/*.{html,js,ts,jsx,tsx}',
  ],
  
  theme: {
    extend: {
      // 只扩展需要的配置
      colors: {
        primary: '#3b82f6',
      },
    },
  },
  
  // 禁用不需要的核心插件
  corePlugins: {
    float: false,
    clear: false,
    skew: false,
    caretColor: false,
    sepia: false,
  },
}

10.3.2 并行构建

// webpack.config.js - Webpack 并行构建
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  mode: 'production',
  
  entry: {
    main: './src/index.js',
    styles: './src/styles.css',
  },
  
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  require('tailwindcss'),
                  require('autoprefixer'),
                ],
              },
            },
          },
        ],
      },
    ],
  },
  
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
  ],
  
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true, // 并行压缩
      }),
      new OptimizeCSSAssetsPlugin({
        cssProcessorOptions: {
          map: {
            inline: false,
            annotation: true,
          },
        },
      }),
    ],
    
    splitChunks: {
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.css$/,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
}

10.3.3 缓存策略

// build-cache.js - 构建缓存策略
const fs = require('fs')
const path = require('path')
const crypto = require('crypto')

class BuildCache {
  constructor(cacheDir = '.cache') {
    this.cacheDir = cacheDir
    this.ensureCacheDir()
  }
  
  ensureCacheDir() {
    if (!fs.existsSync(this.cacheDir)) {
      fs.mkdirSync(this.cacheDir, { recursive: true })
    }
  }
  
  getFileHash(filePath) {
    const content = fs.readFileSync(filePath)
    return crypto.createHash('md5').update(content).digest('hex')
  }
  
  getCacheKey(files) {
    const hashes = files.map(file => this.getFileHash(file))
    return crypto.createHash('md5').update(hashes.join('')).digest('hex')
  }
  
  get(key) {
    const cachePath = path.join(this.cacheDir, `${key}.json`)
    if (fs.existsSync(cachePath)) {
      return JSON.parse(fs.readFileSync(cachePath, 'utf8'))
    }
    return null
  }
  
  set(key, data) {
    const cachePath = path.join(this.cacheDir, `${key}.json`)
    fs.writeFileSync(cachePath, JSON.stringify(data, null, 2))
  }
  
  shouldRebuild(sourceFiles, outputFile) {
    if (!fs.existsSync(outputFile)) {
      return true
    }
    
    const outputStat = fs.statSync(outputFile)
    
    for (const sourceFile of sourceFiles) {
      if (fs.existsSync(sourceFile)) {
        const sourceStat = fs.statSync(sourceFile)
        if (sourceStat.mtime > outputStat.mtime) {
          return true
        }
      }
    }
    
    return false
  }
}

// 使用示例
const cache = new BuildCache()
const sourceFiles = ['./src/styles.css', './tailwind.config.js']
const outputFile = './dist/styles.css'

if (cache.shouldRebuild(sourceFiles, outputFile)) {
  console.log('🔄 需要重新构建 CSS')
  // 执行构建逻辑
} else {
  console.log('✅ CSS 文件是最新的,跳过构建')
}

module.exports = BuildCache

10.3.4 增量构建

// incremental-build.js
const chokidar = require('chokidar')
const { execSync } = require('child_process')
const BuildCache = require('./build-cache')

class IncrementalBuilder {
  constructor(options = {}) {
    this.cache = new BuildCache()
    this.watchPaths = options.watchPaths || ['./src/**/*']
    this.buildCommand = options.buildCommand || 'npm run build-css'
    this.debounceTime = options.debounceTime || 300
    this.buildTimeout = null
  }
  
  start() {
    console.log('🚀 启动增量构建监听...')
    
    const watcher = chokidar.watch(this.watchPaths, {
      ignored: /(^|[\/\\])\../, // 忽略隐藏文件
      persistent: true,
    })
    
    watcher.on('change', (path) => {
      console.log(`📝 文件变更: ${path}`)
      this.scheduleBuild()
    })
    
    watcher.on('add', (path) => {
      console.log(`➕ 新增文件: ${path}`)
      this.scheduleBuild()
    })
    
    watcher.on('unlink', (path) => {
      console.log(`🗑️  删除文件: ${path}`)
      this.scheduleBuild()
    })
    
    // 初始构建
    this.build()
  }
  
  scheduleBuild() {
    if (this.buildTimeout) {
      clearTimeout(this.buildTimeout)
    }
    
    this.buildTimeout = setTimeout(() => {
      this.build()
    }, this.debounceTime)
  }
  
  build() {
    const startTime = Date.now()
    
    try {
      console.log('🔨 开始构建...')
      execSync(this.buildCommand, { stdio: 'inherit' })
      
      const duration = Date.now() - startTime
      console.log(`✅ 构建完成 (${duration}ms)`)
      
    } catch (error) {
      console.error('❌ 构建失败:', error.message)
    }
  }
}

// 使用示例
const builder = new IncrementalBuilder({
  watchPaths: ['./src/**/*.{html,js,jsx,ts,tsx}', './tailwind.config.js'],
  buildCommand: 'tailwindcss -i ./src/input.css -o ./dist/output.css',
  debounceTime: 200,
})

builder.start()

10.4 运行时性能优化

10.4.1 CSS 加载优化

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>性能优化示例</title>
    
    <!-- 关键 CSS 内联 -->
    <style>
        /* 首屏关键样式 */
        body {
            font-family: system-ui, -apple-system, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #ffffff;
            color: #1f2937;
        }
        
        .hero {
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }
        
        .loading {
            display: inline-block;
            width: 20px;
            height: 20px;
            border: 3px solid rgba(255,255,255,.3);
            border-radius: 50%;
            border-top-color: #fff;
            animation: spin 1s ease-in-out infinite;
        }
        
        @keyframes spin {
            to { transform: rotate(360deg); }
        }
    </style>
    
    <!-- 预加载关键资源 -->
    <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
    <link rel="preload" href="/css/critical.css" as="style">
    
    <!-- 异步加载非关键 CSS -->
    <link rel="preload" href="/css/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
    <noscript><link rel="stylesheet" href="/css/styles.css"></noscript>
    
    <!-- DNS 预解析 -->
    <link rel="dns-prefetch" href="//fonts.googleapis.com">
    <link rel="dns-prefetch" href="//api.example.com">
</head>
<body>
    <div class="hero">
        <div class="text-center">
            <h1 class="text-4xl font-bold mb-4">性能优化示例</h1>
            <div class="loading"></div>
        </div>
    </div>
    
    <!-- 延迟加载的内容 -->
    <div id="content" style="display: none;">
        <!-- 页面主要内容 -->
    </div>
    
    <script>
        // 延迟加载非关键内容
        window.addEventListener('load', function() {
            setTimeout(function() {
                document.getElementById('content').style.display = 'block';
            }, 100);
        });
        
        // 异步加载 CSS 的 polyfill
        (function() {
            var links = document.querySelectorAll('link[rel="preload"][as="style"]');
            for (var i = 0; i < links.length; i++) {
                var link = links[i];
                if (link.onload === null) {
                    link.rel = 'stylesheet';
                }
            }
        })();
    </script>
</body>
</html>

10.4.2 图片优化

<!-- 响应式图片优化 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
    <div class="bg-white rounded-lg shadow-md overflow-hidden">
        <!-- 使用 picture 元素进行响应式图片 -->
        <picture>
            <!-- WebP 格式优先 -->
            <source 
                srcset="/images/card-1-320.webp 320w,
                        /images/card-1-640.webp 640w,
                        /images/card-1-960.webp 960w"
                sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
                type="image/webp"
            >
            <!-- 回退到 JPEG -->
            <img 
                src="/images/card-1-640.jpg"
                srcset="/images/card-1-320.jpg 320w,
                        /images/card-1-640.jpg 640w,
                        /images/card-1-960.jpg 960w"
                sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
                alt="卡片图片"
                class="w-full h-48 object-cover"
                loading="lazy"
                decoding="async"
            >
        </picture>
        
        <div class="p-6">
            <h3 class="text-lg font-semibold mb-2">卡片标题</h3>
            <p class="text-gray-600">卡片描述内容...</p>
        </div>
    </div>
</div>

<!-- 懒加载图片组件 -->
<div class="lazy-image-container">
    <img 
        data-src="/images/large-image.jpg"
        data-srcset="/images/large-image-320.jpg 320w,
                     /images/large-image-640.jpg 640w,
                     /images/large-image-1280.jpg 1280w"
        data-sizes="(max-width: 768px) 100vw, 50vw"
        alt="大图片"
        class="w-full h-auto opacity-0 transition-opacity duration-300 lazy"
    >
    <!-- 占位符 -->
    <div class="absolute inset-0 bg-gray-200 animate-pulse flex items-center justify-center">
        <svg class="w-8 h-8 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
            <path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"></path>
        </svg>
    </div>
</div>

<script>
// 图片懒加载实现
class LazyImageLoader {
    constructor() {
        this.images = document.querySelectorAll('img.lazy');
        this.imageObserver = null;
        this.init();
    }
    
    init() {
        if ('IntersectionObserver' in window) {
            this.imageObserver = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        this.loadImage(entry.target);
                        this.imageObserver.unobserve(entry.target);
                    }
                });
            }, {
                rootMargin: '50px 0px',
                threshold: 0.01
            });
            
            this.images.forEach(img => this.imageObserver.observe(img));
        } else {
            // 回退方案
            this.images.forEach(img => this.loadImage(img));
        }
    }
    
    loadImage(img) {
        const src = img.dataset.src;
        const srcset = img.dataset.srcset;
        const sizes = img.dataset.sizes;
        
        if (src) {
            img.src = src;
        }
        
        if (srcset) {
            img.srcset = srcset;
        }
        
        if (sizes) {
            img.sizes = sizes;
        }
        
        img.onload = () => {
            img.classList.remove('opacity-0');
            img.classList.add('opacity-100');
            
            // 隐藏占位符
            const placeholder = img.nextElementSibling;
            if (placeholder && placeholder.classList.contains('animate-pulse')) {
                placeholder.style.display = 'none';
            }
        };
        
        img.classList.remove('lazy');
    }
}

// 初始化懒加载
new LazyImageLoader();
</script>

10.4.3 JavaScript 优化

// performance-utils.js - 性能工具函数
class PerformanceUtils {
    // 防抖函数
    static debounce(func, wait, immediate = false) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                timeout = null;
                if (!immediate) func(...args);
            };
            const callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func(...args);
        };
    }
    
    // 节流函数
    static throttle(func, limit) {
        let inThrottle;
        return function(...args) {
            if (!inThrottle) {
                func.apply(this, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        };
    }
    
    // 虚拟滚动
    static createVirtualScroll(container, items, itemHeight, renderItem) {
        const containerHeight = container.clientHeight;
        const visibleCount = Math.ceil(containerHeight / itemHeight) + 2;
        let scrollTop = 0;
        let startIndex = 0;
        
        const viewport = document.createElement('div');
        viewport.style.height = `${items.length * itemHeight}px`;
        viewport.style.position = 'relative';
        
        const content = document.createElement('div');
        content.style.position = 'absolute';
        content.style.top = '0';
        content.style.width = '100%';
        
        viewport.appendChild(content);
        container.appendChild(viewport);
        
        const updateVisibleItems = () => {
            startIndex = Math.floor(scrollTop / itemHeight);
            const endIndex = Math.min(startIndex + visibleCount, items.length);
            
            content.style.transform = `translateY(${startIndex * itemHeight}px)`;
            content.innerHTML = '';
            
            for (let i = startIndex; i < endIndex; i++) {
                const item = renderItem(items[i], i);
                item.style.height = `${itemHeight}px`;
                content.appendChild(item);
            }
        };
        
        container.addEventListener('scroll', this.throttle(() => {
            scrollTop = container.scrollTop;
            updateVisibleItems();
        }, 16));
        
        updateVisibleItems();
    }
    
    // 性能监控
    static measurePerformance(name, fn) {
        return async (...args) => {
            const start = performance.now();
            const result = await fn(...args);
            const end = performance.now();
            
            console.log(`⏱️  ${name}: ${(end - start).toFixed(2)}ms`);
            
            // 发送到分析服务
            if (window.gtag) {
                window.gtag('event', 'timing_complete', {
                    name: name,
                    value: Math.round(end - start)
                });
            }
            
            return result;
        };
    }
    
    // 内存使用监控
    static monitorMemory() {
        if (performance.memory) {
            const memory = performance.memory;
            console.log('💾 内存使用情况:');
            console.log(`已使用: ${(memory.usedJSHeapSize / 1048576).toFixed(2)} MB`);
            console.log(`总计: ${(memory.totalJSHeapSize / 1048576).toFixed(2)} MB`);
            console.log(`限制: ${(memory.jsHeapSizeLimit / 1048576).toFixed(2)} MB`);
        }
    }
    
    // 检测性能问题
    static detectPerformanceIssues() {
        // 检测长任务
        if ('PerformanceObserver' in window) {
            const observer = new PerformanceObserver((list) => {
                list.getEntries().forEach((entry) => {
                    if (entry.duration > 50) {
                        console.warn(`🐌 检测到长任务: ${entry.name} (${entry.duration.toFixed(2)}ms)`);
                    }
                });
            });
            
            observer.observe({ entryTypes: ['longtask'] });
        }
        
        // 检测布局偏移
        if ('PerformanceObserver' in window) {
            const observer = new PerformanceObserver((list) => {
                let clsValue = 0;
                list.getEntries().forEach((entry) => {
                    if (!entry.hadRecentInput) {
                        clsValue += entry.value;
                    }
                });
                
                if (clsValue > 0.1) {
                    console.warn(`📐 检测到布局偏移: ${clsValue.toFixed(4)}`);
                }
            });
            
            observer.observe({ entryTypes: ['layout-shift'] });
        }
    }
}

// 使用示例
const optimizedSearch = PerformanceUtils.debounce((query) => {
    // 搜索逻辑
    console.log('搜索:', query);
}, 300);

const optimizedScroll = PerformanceUtils.throttle(() => {
    // 滚动处理逻辑
    console.log('滚动事件');
}, 16);

// 性能监控
const monitoredFunction = PerformanceUtils.measurePerformance('数据加载', async () => {
    // 异步数据加载
    await new Promise(resolve => setTimeout(resolve, 1000));
    return '数据';
});

// 启动性能监控
PerformanceUtils.detectPerformanceIssues();
setInterval(() => {
    PerformanceUtils.monitorMemory();
}, 30000);

10.5 用户体验优化

10.5.1 加载状态优化

<!-- 加载状态组件 -->
<div class="loading-states-demo">
    <!-- 骨架屏加载 -->
    <div class="skeleton-loader mb-8">
        <h3 class="text-lg font-semibold mb-4">骨架屏加载</h3>
        <div class="animate-pulse">
            <div class="flex space-x-4">
                <div class="rounded-full bg-gray-300 h-12 w-12"></div>
                <div class="flex-1 space-y-2 py-1">
                    <div class="h-4 bg-gray-300 rounded w-3/4"></div>
                    <div class="h-3 bg-gray-300 rounded w-1/2"></div>
                </div>
            </div>
            <div class="mt-4 space-y-3">
                <div class="h-3 bg-gray-300 rounded"></div>
                <div class="h-3 bg-gray-300 rounded w-5/6"></div>
                <div class="h-3 bg-gray-300 rounded w-4/6"></div>
            </div>
        </div>
    </div>
    
    <!-- 进度条加载 -->
    <div class="progress-loader mb-8">
        <h3 class="text-lg font-semibold mb-4">进度条加载</h3>
        <div class="w-full bg-gray-200 rounded-full h-2">
            <div class="bg-blue-500 h-2 rounded-full transition-all duration-300 ease-out" 
                 style="width: 0%" 
                 id="progress-bar"></div>
        </div>
        <p class="text-sm text-gray-600 mt-2" id="progress-text">加载中... 0%</p>
    </div>
    
    <!-- 旋转加载器 -->
    <div class="spinner-loader mb-8">
        <h3 class="text-lg font-semibold mb-4">旋转加载器</h3>
        <div class="flex items-center space-x-4">
            <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
            <div class="flex space-x-1">
                <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce"></div>
                <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0.1s"></div>
                <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
            </div>
            <div class="animate-pulse flex space-x-1">
                <div class="w-3 h-3 bg-blue-500 rounded-full"></div>
                <div class="w-3 h-3 bg-blue-500 rounded-full"></div>
                <div class="w-3 h-3 bg-blue-500 rounded-full"></div>
            </div>
        </div>
    </div>
</div>

<script>
// 进度条动画
function animateProgress() {
    const progressBar = document.getElementById('progress-bar');
    const progressText = document.getElementById('progress-text');
    let progress = 0;
    
    const interval = setInterval(() => {
        progress += Math.random() * 15;
        if (progress > 100) {
            progress = 100;
            clearInterval(interval);
        }
        
        progressBar.style.width = `${progress}%`;
        progressText.textContent = `加载中... ${Math.round(progress)}%`;
        
        if (progress === 100) {
            progressText.textContent = '加载完成!';
        }
    }, 200);
}

// 启动进度条动画
animateProgress();
</script>

10.5.2 错误状态处理

<!-- 错误状态组件 -->
<div class="error-states-demo space-y-8">
    <!-- 网络错误 -->
    <div class="bg-red-50 border border-red-200 rounded-lg p-6">
        <div class="flex items-center">
            <div class="flex-shrink-0">
                <svg class="h-8 w-8 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                </svg>
            </div>
            <div class="ml-3 flex-1">
                <h3 class="text-sm font-medium text-red-800">网络连接错误</h3>
                <p class="mt-1 text-sm text-red-700">无法连接到服务器,请检查您的网络连接。</p>
                <div class="mt-4">
                    <button class="bg-red-100 hover:bg-red-200 text-red-800 px-4 py-2 rounded-md text-sm font-medium transition-colors duration-200">
                        重试
                    </button>
                </div>
            </div>
        </div>
    </div>
    
    <!-- 404 错误 -->
    <div class="text-center py-12">
        <svg class="mx-auto h-24 w-24 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.47-.881-6.08-2.33" />
        </svg>
        <h2 class="mt-6 text-3xl font-bold text-gray-900">页面未找到</h2>
        <p class="mt-2 text-lg text-gray-600">抱歉,您访问的页面不存在。</p>
        <div class="mt-6">
            <button class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-lg font-medium transition-colors duration-200">
                返回首页
            </button>
        </div>
    </div>
    
    <!-- 空状态 -->
    <div class="text-center py-12">
        <svg class="mx-auto h-16 w-16 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
        </svg>
        <h3 class="mt-4 text-lg font-medium text-gray-900">暂无数据</h3>
        <p class="mt-2 text-gray-600">还没有任何内容,开始创建第一个项目吧!</p>
        <div class="mt-6">
            <button class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg font-medium transition-colors duration-200">
                创建项目
            </button>
        </div>
    </div>
</div>

10.5.3 响应式优化

/* 响应式优化 CSS */
@layer utilities {
    /* 容器查询 */
    .container-responsive {
        container-type: inline-size;
    }
    
    @container (min-width: 400px) {
        .container-responsive .card {
            @apply grid-cols-2;
        }
    }
    
    @container (min-width: 600px) {
        .container-responsive .card {
            @apply grid-cols-3;
        }
    }
    
    /* 字体大小响应式 */
    .text-responsive {
        font-size: clamp(1rem, 2.5vw, 2rem);
    }
    
    /* 间距响应式 */
    .spacing-responsive {
        padding: clamp(1rem, 5vw, 3rem);
    }
    
    /* 网格响应式 */
    .grid-responsive {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
        gap: clamp(1rem, 3vw, 2rem);
    }
}
<!-- 响应式组件示例 -->
<div class="container-responsive">
    <div class="grid grid-cols-1 card gap-4 p-4">
        <div class="bg-white rounded-lg shadow-md p-6">
            <h2 class="text-responsive font-bold mb-4">响应式标题</h2>
            <p class="text-gray-600">这是一个响应式卡片组件,会根据容器大小自动调整布局。</p>
        </div>
        
        <div class="bg-white rounded-lg shadow-md p-6">
            <h2 class="text-responsive font-bold mb-4">自适应内容</h2>
            <p class="text-gray-600">使用容器查询和 clamp() 函数实现真正的响应式设计。</p>
        </div>
        
        <div class="bg-white rounded-lg shadow-md p-6">
            <h2 class="text-responsive font-bold mb-4">流体布局</h2>
            <p class="text-gray-600">布局会根据可用空间自动调整,提供最佳的用户体验。</p>
        </div>
    </div>
</div>

10.6 性能监控与分析

10.6.1 性能指标收集

// performance-monitor.js
class PerformanceMonitor {
    constructor(options = {}) {
        this.apiEndpoint = options.apiEndpoint || '/api/performance';
        this.sampleRate = options.sampleRate || 0.1; // 10% 采样率
        this.metrics = {};
        this.init();
    }
    
    init() {
        // 监听页面加载性能
        window.addEventListener('load', () => {
            setTimeout(() => this.collectLoadMetrics(), 0);
        });
        
        // 监听 Web Vitals
        this.observeWebVitals();
        
        // 监听资源加载
        this.observeResourceTiming();
        
        // 监听用户交互
        this.observeUserInteractions();
    }
    
    collectLoadMetrics() {
        const navigation = performance.getEntriesByType('navigation')[0];
        const paint = performance.getEntriesByType('paint');
        
        this.metrics.loadTime = {
            domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
            loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
            firstPaint: paint.find(p => p.name === 'first-paint')?.startTime || 0,
            firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0,
            ttfb: navigation.responseStart - navigation.requestStart,
        };
        
        this.sendMetrics('load', this.metrics.loadTime);
    }
    
    observeWebVitals() {
        // Largest Contentful Paint (LCP)
        if ('PerformanceObserver' in window) {
            const lcpObserver = new PerformanceObserver((list) => {
                const entries = list.getEntries();
                const lastEntry = entries[entries.length - 1];
                
                this.metrics.lcp = lastEntry.startTime;
                this.sendMetrics('lcp', { value: lastEntry.startTime });
            });
            
            lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
        }
        
        // First Input Delay (FID)
        if ('PerformanceObserver' in window) {
            const fidObserver = new PerformanceObserver((list) => {
                list.getEntries().forEach((entry) => {
                    this.metrics.fid = entry.processingStart - entry.startTime;
                    this.sendMetrics('fid', { value: entry.processingStart - entry.startTime });
                });
            });
            
            fidObserver.observe({ entryTypes: ['first-input'] });
        }
        
        // Cumulative Layout Shift (CLS)
        if ('PerformanceObserver' in window) {
            let clsValue = 0;
            const clsObserver = new PerformanceObserver((list) => {
                list.getEntries().forEach((entry) => {
                    if (!entry.hadRecentInput) {
                        clsValue += entry.value;
                    }
                });
                
                this.metrics.cls = clsValue;
            });
            
            clsObserver.observe({ entryTypes: ['layout-shift'] });
            
            // 页面卸载时发送 CLS 数据
            window.addEventListener('beforeunload', () => {
                this.sendMetrics('cls', { value: clsValue });
            });
        }
    }
    
    observeResourceTiming() {
        if ('PerformanceObserver' in window) {
            const resourceObserver = new PerformanceObserver((list) => {
                list.getEntries().forEach((entry) => {
                    if (entry.name.includes('.css')) {
                        this.sendMetrics('css-load', {
                            url: entry.name,
                            duration: entry.duration,
                            size: entry.transferSize,
                        });
                    }
                });
            });
            
            resourceObserver.observe({ entryTypes: ['resource'] });
        }
    }
    
    observeUserInteractions() {
        // 点击响应时间
        document.addEventListener('click', (event) => {
            const startTime = performance.now();
            
            requestAnimationFrame(() => {
                const endTime = performance.now();
                const duration = endTime - startTime;
                
                if (duration > 100) { // 只记录较慢的交互
                    this.sendMetrics('interaction', {
                        type: 'click',
                        target: event.target.tagName,
                        duration: duration,
                    });
                }
            });
        });
    }
    
    sendMetrics(type, data) {
        // 采样控制
        if (Math.random() > this.sampleRate) {
            return;
        }
        
        const payload = {
            type,
            data,
            timestamp: Date.now(),
            url: window.location.href,
            userAgent: navigator.userAgent,
            connection: this.getConnectionInfo(),
        };
        
        // 使用 sendBeacon 或 fetch 发送数据
        if (navigator.sendBeacon) {
            navigator.sendBeacon(this.apiEndpoint, JSON.stringify(payload));
        } else {
            fetch(this.apiEndpoint, {
                method: 'POST',
                body: JSON.stringify(payload),
                headers: {
                    'Content-Type': 'application/json',
                },
                keepalive: true,
            }).catch(console.error);
        }
    }
    
    getConnectionInfo() {
        if ('connection' in navigator) {
            const conn = navigator.connection;
            return {
                effectiveType: conn.effectiveType,
                downlink: conn.downlink,
                rtt: conn.rtt,
            };
        }
        return null;
    }
    
    // 手动记录自定义指标
    recordCustomMetric(name, value, tags = {}) {
        this.sendMetrics('custom', {
            name,
            value,
            tags,
        });
    }
}

// 初始化性能监控
const monitor = new PerformanceMonitor({
    apiEndpoint: '/api/performance',
    sampleRate: 0.1,
});

// 使用示例
monitor.recordCustomMetric('css-build-size', 125.5, { version: '1.0.0' });

10.6.2 性能报告生成

”`javascript // performance-report.js class PerformanceReport { constructor() { this.data = []; this.thresholds = { lcp: 2500, // 2.5s fid: 100, // 100ms cls: 0.1, // 0.1 ttfb: 600, // 600ms }; }

async generateReport() {
    const report = {
        timestamp: new Date().toISOString(),
        summary: await this.getSummary(),
        details: await this.getDetails(),
        recommendations: this.getRecommendations(),
    };

    return report;
}

async getSummary() {
    const metrics = await this.collectCurrentMetrics();

    return {
        score: this.calculateScore(metrics),
        metrics: metrics,
        status: this.getStatus(metrics),
    };
}

async collectCurrentMetrics() {
    return new Promise((resolve) => {
        const metrics = {};

        // 收集导航时间
        const navigation = performance.getEntriesByType('navigation')[0];
        if (navigation) {
            metrics.ttfb = navigation.responseStart - navigation.requestStart;
            metrics.domContentLoaded = navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart;
            metrics.loadComplete = navigation.loadEventEnd - navigation.loadEventStart;
        }

        // 收集绘制时间
        const paint = performance.getEntriesByType('paint');
        metrics.firstPaint = paint.find(p => p.name === 'first-paint')?.startTime || 0;
        metrics.firstContentfulPaint = paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0;

        // 收集资源信息
        const resources = performance.getEntriesByType('resource');
        const cssResources = resources.filter(r => r.name.includes('.css'));

        metrics.cssLoadTime = cssResources.reduce((total, resource) => {
            return total + resource.duration;
        }, 0);

        metrics.cssSize = cssResources.reduce((total, resource) => {
            return total + (resource.transferSize || 0);
        }, 0);

        resolve(metrics);
    });
}

calculateScore(metrics) {
    let score = 100;

    // LCP 评分
    if (metrics.firstContentfulPaint > this.thresholds.lcp) {
        score -= 20;
    } else if (metrics.firstContentfulPaint > this.thresholds.lcp * 0.75) {
        score -= 10;
    }

    // TTFB 评分
    if (metrics.ttfb > this.thresholds.ttfb) {
        score -= 15;
    } else if (metrics.ttfb > this.thresholds.ttfb * 0.75) {
        score -= 8;
    }

    // CSS 大小评分
    if (metrics.cssSize > 100 * 1024) { // 100KB
        score -= 15;
    } else if (metrics.cssSize > 50 * 1024) { // 50KB
        score -= 8;
    }

    // CSS 加载时间评分
    if (metrics.cssLoadTime > 1000) { // 1s
        score -= 10;
    } else if (metrics.cssLoadTime > 500) { // 500ms
        score -= 5;
    }

    return Math.max(0, score);
}

getStatus(metrics) {
    const score = this.calculateScore(metrics);

    if (score >= 90) return 'excellent';
    if (score >= 75) return 'good';
    if (score >= 60) return 'needs-improvement';
    return 'poor';
}

getRecommendations() {
    const recommendations = [];

    // 基于当前指标生成建议
    recommendations.push({
        category: 'CSS 优化',
        items: [
            '启用 CSS 压缩和合并',
            '移除未使用的 CSS 规则',
            '使用 CSS 预加载',
            '考虑关键 CSS 内联',
        ],
    });

    recommendations.push({
        category: '图片优化',
        items: [
            '使用现代图片格式 (WebP, AVIF)',
            '实现图片懒加载',
            '优化图片尺寸和质量',
            '使用响应式图片',
        ],
    });

    recommendations.push({
        category: '缓存策略',
        items: [
            '设置适当的缓存头',
            '使用 CDN 加速',
            '启用浏览器缓存',
            '实现服务端缓存',
        ],
    });

    return recommendations;
}

async exportReport(format = 'json') {
    const report = await this.generateReport();

    switch (format) {
        case 'json':
            return JSON.stringify(report, null, 2);

        case 'html':
            return this.generateHTMLReport(report);

        case 'csv':
            return this.generateCSVReport(report);

        default:
            return report;
    }
}

generateHTMLReport(report) {
    return `

<!DOCTYPE html> 性能报告

性能分析报告

    <!-- 总体评分 -->
    <div class="bg-white rounded-lg shadow-md p-6 mb-8">
        <h2 class="text-xl font-semibold mb-4">总体评分</h2>
        <div class="flex items-center space-x-4">
            <div class="text-4xl font-bold ${this.getScoreColor(report.summary.score)}">
                ${report.summary.score}
            </div>
            <div>
                <div class="text-lg font-medium ${this.getStatusColor(report.summary.status)}">
                    ${this.getStatusText(report.summary.status)}
                </div>
                <div class="text-gray-600">生成时间: ${new Date(report.timestamp).toLocaleString()}</div>
            </div>
        </div>
    </div>

    <!-- 详细指标 -->
    <div class="bg-white rounded-lg shadow-md p-6 mb-8">
        <h2 class="text-xl font-semibold mb-4">详细指标</h2>
        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
            ${Object.entries(report.summary.metrics).map(([key, value]) => `
                <div class="bg-gray-50 p-4 rounded">
                    <div class="text-sm text-gray-600">${this.getMetricName(key)}</div>
                    <div class="text-lg font-semibold">${this.formatMetricValue(key, value)}</div>
                </div>
            `).join('')}
        </div>
    </div>

    <!-- 优化建议 -->
    <div class="bg-white rounded-lg shadow-md p-6">
        <h2 class="text-xl font-semibold mb-4">优化建议</h2>
        ${report.recommendations.map(category => `
            <div class="mb-6">
                <h3 class="text-lg font-medium mb-2">${category.category}</h3>
                <ul class="list-disc list-inside space-y-1">
                    ${category.items.map(item => `<li class="text-gray-700">${item}</li>`).join('')}
                </ul>
            </div>
        `).join('')}
    </div>
</div>

`; }