发布于 2024 年 11 月 17 日,星期日
前端优化图片渐进式加载的核心在于提升用户体验,通过逐步加载图片的方式,减少页面加载时间,避免用户长时间等待。文章可能介绍如何使用渐进式JPEG格式、懒加载技术、图片压缩和WebP格式等方法,来优化图片加载性能。此外,还可能探讨如何利用Intersection Observer API或滚动事件监听器,实现图片的智能加载,确保在用户可见区域内才加载图片,从而减少不必要的资源消耗。文章还可能分析不同浏览器对这些技术的支持情况,以及如何通过代码优化和性能监控工具,进一步提升图片加载效率。
src
可能是动态的,也就是一个字符串 string
url, 当我们指定了 placeholder="blur"
时,还必须添加 blurDataURL
https://nextjs.org/docs/pages/api-reference/components/image#blurdataurl 属性,import Image from 'next/image';// Pixel GIF code adapted from https://stackoverflow.com/a/33919020/266535const keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';const triplet = (e1: number, e2: number, e3: number) => keyStr.charAt(e1 >> 2) + keyStr.charAt(((e1 & 3) << 4) | (e2 >> 4)) + keyStr.charAt(((e2 & 15) << 2) | (e3 >> 6)) + keyStr.charAt(e3 & 63);const rgbDataURL = (r: number, g: number, b: number) => `data:image/gif;base64,R0lGODlhAQABAPAA${
triplet(0, r, g) + triplet(b, 255, 255)
}/yH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==`;const Color = () => ( <div>
<h1>Image Component With Color Data URL</h1>
<Image
alt="Dog"
src="/dog.jpg"
placeholder="blur"
blurDataURL={rgbDataURL(237, 181, 6)}
width={750}
height={1000}
style={{
maxWidth: '100%',
height: 'auto'
}}
/>
<Image
alt="Cat"
src="/cat.jpg"
placeholder="blur"
blurDataURL={rgbDataURL(2, 129, 210)}
width={750}
height={1000}
style={{
maxWidth: '100%',
height: 'auto'
}}
/>
</div>);export default Color;
next.config.js
中配置 placeholder
https://nextjs.org/docs/pages/api-reference/components/image#placeholder 为 color
,然后使用 backgroundColor
属性// next.config.jsmodule.exports = { images: { placeholder: 'color', backgroundColor: '#121212' }};
// 使用<Image src="/path/to/image.jpg" alt="image title" width={500} height={500} placeholder="color" />
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>渐进式图片加载</title> <style>
.placeholder {
background-color: #f6f6f6;
background-size: cover;
background-repeat: no-repeat;
position: relative;
overflow: hidden;
}
.placeholder img {
position: absolute;
opacity: 0;
top: 0;
left: 0;
width: 100%;
transition: opacity 1s linear;
}
.placeholder img.loaded {
opacity: 1;
}
.img-small {
filter: blur(50px);
transform: scale(1);
}
</style> </head> <body> <div
class="placeholder"
data-large="https://qncdn.mopic.mozigu.net/work/143/24/42b204ae3ade4f38/1_sg-uLNm73whmdOgKlrQdZA.jpg"
> <img
src="https://qncdn.mopic.mozigu.net/work/143/24/5307e9778a944f93/1_sg-uLNm73whmdOgKlrQdZA.jpg"
class="img-small"
/> <div style="padding-bottom: 66.6%"></div> </div> </body></html><script>
window.onload = function () {
var placeholder = document.querySelector('.placeholder'),
small = placeholder.querySelector('.img-small');
// 1. 显示小图并加载
var img = new Image();
img.src = small.src;
img.onload = function () {
small.classList.add('loaded');
};
// 2. 加载大图
var imgLarge = new Image();
imgLarge.src = placeholder.dataset.large;
imgLarge.onload = function () {
imgLarge.classList.add('loaded');
};
placeholder.appendChild(imgLarge);
};
</script>
// progressive-image,tsx'use client';import React, { useState, useEffect } from 'react';import imageCompression from 'browser-image-compression';interface ProgressiveImageProps { src: string; alt?: string; width?: number; height?: number; layout?: 'fixed' | 'responsive' | 'fill' | 'intrinsic'; className?: string; style?: React.CSSProperties;}export const ProgressiveImage: React.FC<ProgressiveImageProps> = ({
src,
alt = '',
width,
height,
layout = 'responsive',
className = '',
style = {}
}) => { const [currentSrc, setCurrentSrc] = useState<string>(src); const [isLoading, setIsLoading] = useState<boolean>(true); const [blurLevel, setBlurLevel] = useState<number>(20); useEffect(() => { let isMounted = true; const loadImage = async () => { try { // 加载并压缩原始图片 const response = await fetch(src); const blob = await response.blob(); // 生成低质量预览图 const tinyOptions = { maxSizeMB: 0.0002, maxWidthOrHeight: 16, useWebWorker: true, initialQuality: 0.1 }; const tinyBlob = await imageCompression(blob, tinyOptions); if (isMounted) { const tinyUrl = URL.createObjectURL(tinyBlob); setCurrentSrc(tinyUrl); // 开始逐渐减小模糊度 startSmoothTransition(); } // 加载原始图片 const highQualityImage = new Image(); highQualityImage.src = src; highQualityImage.onload = () => { if (isMounted) { setCurrentSrc(src); // 当高质量图片加载完成时,继续平滑过渡 setTimeout(() => { setIsLoading(false); }, 100); } }; } catch (error) { console.error('Error loading image:', error); if (isMounted) { setCurrentSrc(src); setIsLoading(false); } } }; const startSmoothTransition = () => { // 从20px的模糊逐渐过渡到10px const startBlur = 20; const endBlur = 10; const duration = 1000; // 1秒 const steps = 20; const stepDuration = duration / steps; const blurStep = (startBlur - endBlur) / steps; let currentStep = 0; const interval = setInterval(() => { if (currentStep < steps && isMounted) { setBlurLevel(startBlur - blurStep * currentStep); currentStep++; } else { clearInterval(interval); } }, stepDuration); }; setIsLoading(true); setBlurLevel(20); loadImage(); return () => { isMounted = false; if (currentSrc && currentSrc.startsWith('blob:')) { URL.revokeObjectURL(currentSrc); } }; }, [src]); const getContainerStyle = (): React.CSSProperties => { const baseStyle: React.CSSProperties = { position: 'relative', overflow: 'hidden' }; switch (layout) { case 'responsive': return { ...baseStyle, maxWidth: width || '100%', width: '100%' }; case 'fixed': return { ...baseStyle, width: width, height: height }; case 'fill': return { ...baseStyle, width: '100%', height: '100%', position: 'absolute', top: 0, left: 0 }; case 'intrinsic': return { ...baseStyle, maxWidth: width, width: '100%' }; default: return baseStyle; } }; const getImageStyle = (): React.CSSProperties => { const baseStyle: React.CSSProperties = { filter: isLoading ? `blur(${blurLevel}px)` : 'none', transition: 'filter 0.8s ease-in-out', // 增加过渡时间 transform: 'scale(1.1)', // 稍微放大防止模糊时出现边缘 ...style }; switch (layout) { case 'responsive': return { ...baseStyle, width: '100%', height: 'auto', display: 'block' }; case 'fixed': return { ...baseStyle, width: width, height: height }; case 'fill': return { ...baseStyle, width: '100%', height: '100%', objectFit: 'cover' }; case 'intrinsic': return { ...baseStyle, width: '100%', height: 'auto' }; default: return baseStyle; } }; return ( <div className={`${className}`} style={getContainerStyle()}>
{currentSrc && <img src={currentSrc} alt={alt} style={getImageStyle()} />}
</div> );};
// 使用<ProgressiveImage src={photo} alt={short.title} width={300} height={250} layout="responsive" className="h-full min-h-[150px]"/>
placeholder
https://nextjs.org/docs/pages/api-reference/components/image#placeholder 属性提供了个选项 blur
,默认为 empty
blur
会生成一个模糊的预览图像(但这个选项会增加初始加载实践,因为需要时间去生成模糊图片)placeholder="blur"
时,必须使用 import
静态引入图片的方式,这样 Next.js 才会对图片进行渐进式加载的预处理import Image from 'next/image';import mountains from '/public/mountains.jpg';const PlaceholderBlur = () => ( <div>
<h1>Image Component With Placeholder Blur</h1>
<Image
alt="Mountains"
src={mountains}
placeholder="blur"
width={700}
height={475}
style={{
maxWidth: '100%',
height: 'auto'
}}
/>
</div>);export default PlaceholderBlur;