前言
- Hey, 我是 Immerse
- 文章首发于个人博客【https://yaolifeng.com】,更多内容请关注个人博客
- 转载说明:转载请在文章头部注明原文出处及版权声明!
起因
- 最近上线了个人博客,片段页面存在大量图片,在图片加载方面体验很差,可以说是断崖式,从 0-1 完全没有任何过渡(这很影响页面布局和用户体验,对于设定了图片宽高的图片还好,如果没设置,就会有一个图片撑高的过程)
巧合
- 在准备写这篇文章当天前端南玖大佬发表了一篇文章,我直呼大数据牛逼 👍🏻文章: 点击查看
- 这篇文章我们将讨论其他几种方案,闲话少说,言归正传。
- 对于常规的图片优化这里不在赘述,大致如下:
- 压缩图片、使用 CSS sprites、懒加载、预加载、CDN 缓存、合适的图片格式、七牛 CDN 图片参数等等
- 对于常规的图片优化这里不在赘述,大致如下:
探索
- 以下是这篇文章提到的几种方案(因为个人项目基于 Next,所以有些示例代码是 React)
- (1)使用图片主色调
- (2)使用某个颜色
- (3)使用图片的缩略图
- (4)使用模糊 + 压缩图片
- (5)图片占位符
方案 1:使用图片主色调
- 在日常开发中,我们的图片
src
可能是动态的,也就是一个字符串string
url, 当我们指定了placeholder="blur"
时,还必须添加blurDataURL
属性,
import Image from 'next/image';
// Pixel GIF code adapted from https://stackoverflow.com/a/33919020/266535
const 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 = () => (
Image Component With Color Data URL
);
export default Color;
方案 2:使用某个颜色
- 在
next.config.js
中配置placeholder
为color
,然后使用backgroundColor
属性
// next.config.js
module.exports = {
images: {
placeholder: 'color',
backgroundColor: '#121212'
}
};
// 使用
方案 3: 使用图片的缩略图
渐进式图片加载
方案 4:使用模糊+压缩图片
// 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 = ({
src,
alt = '',
width,
height,
layout = 'responsive',
className = '',
style = {}
}) => {
const [currentSrc, setCurrentSrc] = useState(src);
const [isLoading, setIsLoading] = useState(true);
const [blurLevel, setBlurLevel] = useState(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 (
{currentSrc && }
);
};
// 使用
方案 5:图片占位符
- Next.js 的 next/image 组件
placeholder
属性提供了个选项blur
,默认为empty
blur
会生成一个模糊的预览图像(但这个选项会增加初始加载实践,因为需要时间去生成模糊图片)- 注意:如果
placeholder="blur"
时,必须使用import
静态引入图片的方式,这样 Next.js 才会对图片进行渐进式加载的预处理
import Image from 'next/image';
import mountains from '/public/mountains.jpg';
const PlaceholderBlur = () => (
Image Component With Placeholder Blur
);
export default PlaceholderBlur;
总结
- 产品第一印象很重要,良好的用户体验对于产品来说是必需的。
- 感谢阅读,我们下次再见!
玄机博客
© 版权声明
1.本站内容仅供参考,不作为任何法律依据。用户在使用本站内容时,应自行判断其真实性、准确性和完整性,并承担相应风险。
2.本站部分内容来源于互联网,仅用于交流学习研究知识,若侵犯了您的合法权益,请及时邮件或站内私信与本站联系,我们将尽快予以处理。
3.本文采用知识共享 署名4.0国际许可协议 [BY-NC-SA] 进行授权
4.根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
5.本站是非经营性个人站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途
THE END
暂无评论内容