umi图片优化:WebP与AVIF格式支持

umi图片优化:WebP与AVIF格式支持

【免费下载链接】umi A framework in react community ✨ 【免费下载链接】umi 项目地址: https://gitcode.com/GitHub_Trending/um/umi

前言:现代Web图片格式的革命

还在为网站图片加载缓慢而烦恼吗?还在纠结于JPEG和PNG的兼容性与性能平衡吗?现代Web开发已经进入了下一代图片格式的时代,WebP和AVIF格式正在彻底改变我们对图片优化的认知。

本文将深入探讨如何在umi框架中集成WebP和AVIF格式支持,通过实际配置和代码示例,帮助您构建更快速、更高效的现代Web应用。

图片格式对比:传统 vs 现代

格式压缩效率浏览器支持特性适用场景
JPEG中等全平台有损压缩照片、复杂图像
PNG较低全平台无损压缩图标、透明图像
WebPChrome、Edge、Firefox、Safari(14+)有损/无损、透明通用场景
AVIF极高Chrome、Firefox、Opera有损/无损、HDR高质量图像

umi项目中的图片处理架构

umi基于Webpack构建,默认使用url-loaderfile-loader处理静态资源。要支持现代图片格式,我们需要通过chainWebpack进行自定义配置。

基础配置示例

// config/config.ts
export default {
  chainWebpack(memo) {
    // WebP格式配置
    memo.module
      .rule('webp')
      .test(/\.(webp)$/i)
      .use('url-loader')
      .loader(require.resolve('url-loader'))
      .options({
        limit: 8192,
        fallback: require.resolve('file-loader'),
        name: 'static/images/[name].[hash:8].[ext]',
      });

    // AVIF格式配置  
    memo.module
      .rule('avif')
      .test(/\.(avif)$/i)
      .use('url-loader')
      .loader(require.resolve('url-loader'))
      .options({
        limit: 8192,
        fallback: require.resolve('file-loader'),
        name: 'static/images/[name].[hash:8].[ext]',
      });

    return memo;
  },
};

完整的图片优化方案

1. 多格式图片处理配置

// config/config.ts
export default {
  chainWebpack(memo) {
    const imageRule = memo.module.rule('images');
    
    // 清除默认图片规则
    memo.module.rules.delete('images');

    // 重新配置图片处理规则
    memo.module
      .rule('images')
      .test(/\.(png|jpe?g|gif|webp|avif)(\?.*)?$/)
      .use('url-loader')
      .loader(require.resolve('url-loader'))
      .options({
        limit: 4096, // 小于4KB的图片转为base64
        fallback: require.resolve('file-loader'),
        name: 'static/images/[name].[hash:8].[ext]',
        esModule: false,
      });

    return memo;
  },
};

2. 自动格式转换方案

对于需要兼容旧浏览器的场景,可以使用picture元素提供多格式回退:

import React from 'react';

const OptimizedImage = ({ src, alt, className, ...props }) => {
  const getWebpSrc = (originalSrc) => {
    return originalSrc.replace(/\.(jpg|jpeg|png)$/i, '.webp');
  };

  const getAvifSrc = (originalSrc) => {
    return originalSrc.replace(/\.(jpg|jpeg|png)$/i, '.avif');
  };

  return (
    <picture>
      <source srcSet={getAvifSrc(src)} type="image/avif" />
      <source srcSet={getWebpSrc(src)} type="image/webp" />
      <img
        src={src}
        alt={alt}
        className={className}
        loading="lazy"
        {...props}
      />
    </picture>
  );
};

export default OptimizedImage;

构建时图片优化流程

mermaid

性能优化最佳实践

1. 懒加载实现

// components/LazyImage.tsx
import React, { useState, useRef, useEffect } from 'react';

interface LazyImageProps {
  src: string;
  alt: string;
  webpSrc?: string;
  avifSrc?: string;
  className?: string;
  width?: number;
  height?: number;
}

const LazyImage: React.FC<LazyImageProps> = ({
  src,
  alt,
  webpSrc,
  avifSrc,
  className,
  width,
  height,
}) => {
  const [isLoaded, setIsLoaded] = useState(false);
  const [isInView, setIsInView] = useState(false);
  const imgRef = useRef<HTMLImageElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsInView(true);
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return (
    <picture>
      {avifSrc && isInView && (
        <source srcSet={avifSrc} type="image/avif" />
      )}
      {webpSrc && isInView && (
        <source srcSet={webpSrc} type="image/webp" />
      )}
      <img
        ref={imgRef}
        src={isInView ? src : 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='}
        alt={alt}
        className={className}
        width={width}
        height={height}
        loading="lazy"
        onLoad={() => setIsLoaded(true)}
        style={{
          opacity: isLoaded ? 1 : 0,
          transition: 'opacity 0.3s ease-in-out',
        }}
      />
    </picture>
  );
};

export default LazyImage;

2. 响应式图片处理

// utils/imageUtils.ts
export const generateResponsiveSrcSet = (
  baseSrc: string,
  sizes: number[] = [320, 640, 960, 1280, 1920]
): string => {
  return sizes
    .map(size => {
      const webpSrc = baseSrc.replace(/\.(jpg|jpeg|png)$/i, `-${size}w.webp`);
      return `${webpSrc} ${size}w`;
    })
    .join(', ');
};

export const generateSrcSet = (baseSrc: string, format: 'webp' | 'avif' = 'webp') => {
  const sizes = [320, 640, 960, 1280, 1920];
  return sizes
    .map(size => {
      const formattedSrc = baseSrc.replace(
        /\.(jpg|jpeg|png)$/i, 
        `-${size}w.${format}`
      );
      return `${formattedSrc} ${size}w`;
    })
    .join(', ');
};

构建脚本集成

1. 图片压缩和转换脚本

// scripts/optimize-images.js
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');

async function optimizeImage(inputPath, outputPath, options = {}) {
  const { width, height, quality = 80, format = 'webp' } = options;
  
  let image = sharp(inputPath);
  
  if (width || height) {
    image = image.resize(width, height, {
      fit: 'inside',
      withoutEnlargement: true,
    });
  }

  const outputOptions = {};
  
  switch (format) {
    case 'webp':
      outputOptions.quality = quality;
      outputOptions.lossless = quality === 100;
      break;
    case 'avif':
      outputOptions.quality = quality;
      outputOptions.lossless = quality === 100;
      break;
    case 'jpeg':
      outputOptions.quality = quality;
      outputOptions.mozjpeg = true;
      break;
    case 'png':
      outputOptions.compressionLevel = 9;
      break;
  }

  await image[format](outputOptions).toFile(outputPath);
}

async function processDirectory(inputDir, outputDir, formats = ['webp', 'avif']) {
  const files = fs.readdirSync(inputDir);
  
  for (const file of files) {
    const inputPath = path.join(inputDir, file);
    const stat = fs.statSync(inputPath);
    
    if (stat.isDirectory()) {
      const newOutputDir = path.join(outputDir, file);
      if (!fs.existsSync(newOutputDir)) {
        fs.mkdirSync(newOutputDir, { recursive: true });
      }
      await processDirectory(inputPath, newOutputDir, formats);
      continue;
    }

    if (/\.(jpg|jpeg|png)$/i.test(file)) {
      for (const format of formats) {
        const outputFile = file.replace(/\.(jpg|jpeg|png)$/i, `.${format}`);
        const outputPath = path.join(outputDir, outputFile);
        
        try {
          await optimizeImage(inputPath, outputPath, { format });
          console.log(`Converted ${file} to ${format}`);
        } catch (error) {
          console.error(`Error converting ${file} to ${format}:`, error);
        }
      }
    }
  }
}

// 使用示例
// processDirectory('./src/images', './public/images', ['webp', 'avif']);

性能监控和测试

1. 图片加载性能监控

// hooks/useImagePerformance.ts
import { useEffect, useRef } from 'react';

interface ImagePerformanceMetrics {
  loadTime: number;
  size: number;
  format: string;
  naturalWidth: number;
  naturalHeight: number;
}

export const useImagePerformance = (src: string) => {
  const metricsRef = useRef<ImagePerformanceMetrics>({
    loadTime: 0,
    size: 0,
    format: '',
    naturalWidth: 0,
    naturalHeight: 0,
  });

  useEffect(() => {
    const startTime = performance.now();
    let img: HTMLImageElement | null = new Image();

    img.onload = () => {
      const loadTime = performance.now() - startTime;
      
      // 获取图片格式
      const format = src.split('.').pop()?.toLowerCase() || '';
      
      // 模拟获取图片大小(实际项目中可能需要从服务器获取)
      const size = Math.round((img?.naturalWidth * img?.naturalHeight * 4) / 1024);
      
      metricsRef.current = {
        loadTime,
        size,
        format,
        naturalWidth: img?.naturalWidth || 0,
        naturalHeight: img?.naturalHeight || 0,
      };

      // 发送性能数据到监控系统
      sendPerformanceMetrics(metricsRef.current);
      
      img = null;
    };

    img.onerror = () => {
      console.error(`Failed to load image: ${src}`);
      img = null;
    };

    img.src = src;

    return () => {
      if (img) {
        img.onload = null;
        img.onerror = null;
      }
    };
  }, [src]);

  const sendPerformanceMetrics = (metrics: ImagePerformanceMetrics) => {
    // 这里可以集成到您的监控系统
    console.log('Image performance metrics:', metrics);
  };

  return metricsRef.current;
};

总结与最佳实践清单

通过本文的配置和代码示例,您可以在umi项目中实现完整的WebP和AVIF格式支持。以下是关键实践总结:

✅ 必做项目

  1. 配置chainWebpack:正确处理现代图片格式
  2. 使用picture元素:提供多格式回退支持
  3. 实现懒加载:优化页面加载性能
  4. 响应式图片:适配不同屏幕尺寸

🎯 进阶优化

  1. 构建时转换:自动生成多格式版本
  2. 性能监控:跟踪图片加载性能
  3. CDN集成:结合CDN进行智能格式分发
  4. 缓存策略:优化浏览器缓存机制

📊 性能收益

  • WebP:相比JPEG节省25-35%的文件大小
  • AVIF:相比WebP再节省20-30%的文件大小
  • 加载时间:减少50-70%的图片加载时间
  • 用户体验:显著改善LCP(最大内容绘制)指标

通过实施这些优化策略,您的umi应用将获得显著的性能提升,为用户提供更流畅的浏览体验。

【免费下载链接】umi A framework in react community ✨ 【免费下载链接】umi 项目地址: https://gitcode.com/GitHub_Trending/um/umi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值