umi图片优化:WebP与AVIF格式支持
【免费下载链接】umi A framework in react community ✨ 项目地址: https://gitcode.com/GitHub_Trending/um/umi
前言:现代Web图片格式的革命
还在为网站图片加载缓慢而烦恼吗?还在纠结于JPEG和PNG的兼容性与性能平衡吗?现代Web开发已经进入了下一代图片格式的时代,WebP和AVIF格式正在彻底改变我们对图片优化的认知。
本文将深入探讨如何在umi框架中集成WebP和AVIF格式支持,通过实际配置和代码示例,帮助您构建更快速、更高效的现代Web应用。
图片格式对比:传统 vs 现代
| 格式 | 压缩效率 | 浏览器支持 | 特性 | 适用场景 |
|---|---|---|---|---|
| JPEG | 中等 | 全平台 | 有损压缩 | 照片、复杂图像 |
| PNG | 较低 | 全平台 | 无损压缩 | 图标、透明图像 |
| WebP | 高 | Chrome、Edge、Firefox、Safari(14+) | 有损/无损、透明 | 通用场景 |
| AVIF | 极高 | Chrome、Firefox、Opera | 有损/无损、HDR | 高质量图像 |
umi项目中的图片处理架构
umi基于Webpack构建,默认使用url-loader和file-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;
构建时图片优化流程
性能优化最佳实践
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格式支持。以下是关键实践总结:
✅ 必做项目
- 配置chainWebpack:正确处理现代图片格式
- 使用picture元素:提供多格式回退支持
- 实现懒加载:优化页面加载性能
- 响应式图片:适配不同屏幕尺寸
🎯 进阶优化
- 构建时转换:自动生成多格式版本
- 性能监控:跟踪图片加载性能
- CDN集成:结合CDN进行智能格式分发
- 缓存策略:优化浏览器缓存机制
📊 性能收益
- WebP:相比JPEG节省25-35%的文件大小
- AVIF:相比WebP再节省20-30%的文件大小
- 加载时间:减少50-70%的图片加载时间
- 用户体验:显著改善LCP(最大内容绘制)指标
通过实施这些优化策略,您的umi应用将获得显著的性能提升,为用户提供更流畅的浏览体验。
【免费下载链接】umi A framework in react community ✨ 项目地址: https://gitcode.com/GitHub_Trending/um/umi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



