vue-web端实现图片懒加载的方

方案1:使用原生 loading="lazy"(最简单)

复制代码
<template>
	<div class="book-item" v-for="item in list" :key="item.novelID">
		<div class="left-line"></div>
		<!-- 添加 loading="lazy" -->
		<img 
			:src="item.coverUrl" 
			:data-src="item.coverUrl" 
			class="img lazy-img" 
			alt=""
			loading="lazy"
		/>
		<div class="highlight"></div>
		<!-- ... 其他内容 ... -->
	</div>
</template>

<style lang="scss" scoped>
.lazy-img {
	background-color: #f5f5f5; /* 占位背景色 */
	background-image: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
	background-size: 200% 100%;
	animation: loading 1.5s infinite;
}

@keyframes loading {
	0% { background-position: 200% 0; }
	100% { background-position: -200% 0; }
}
</style>

我是用就是这种模式

挺方便快捷的

原生方式最好

方案2:使用 IntersectionObserver API(推荐)

复制代码
<template>
	<div class="book-item" v-for="item in list" :key="item.novelID">
		<div class="left-line"></div>
		<!-- 使用 data-src 存储真实地址,src 使用占位图 -->
		<img 
			:data-src="item.coverUrl" 
			:src="placeholder" 
			class="img lazy-img" 
			alt=""
			ref="lazyImages"
		/>
		<div class="highlight"></div>
		<!-- ... 其他内容 ... -->
	</div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

// 占位图(可以使用base64小图)
const placeholder = ref('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjVmNWY1Ii8+PC9zdmc+');

// 图片引用
const lazyImages = ref([]);

// IntersectionObserver 实例
let observer = null;

// 初始化懒加载
const initLazyLoad = () => {
	// 如果浏览器不支持 IntersectionObserver,降级处理
	if (!('IntersectionObserver' in window)) {
		loadAllImages();
		return;
	}
	
	// 配置观察器
	const options = {
		root: null, // 视口
		rootMargin: '50px', // 提前50px加载
		threshold: 0.01 // 只要1%可见就加载
	};
	
	observer = new IntersectionObserver((entries) => {
		entries.forEach(entry => {
			if (entry.isIntersecting) {
				const img = entry.target;
				loadImage(img);
				observer.unobserve(img); // 加载后停止观察
			}
		});
	}, options);
	
	// 开始观察所有图片
	setTimeout(() => {
		lazyImages.value.forEach(img => {
			if (img && img.dataset.src) {
				observer.observe(img);
			}
		});
	}, 100);
};

// 加载单张图片
const loadImage = (imgElement) => {
	const src = imgElement.dataset.src;
	if (!src) return;
	
	// 创建新Image对象预加载
	const img = new Image();
	img.onload = () => {
		// 图片加载成功后设置到元素
		imgElement.src = src;
		imgElement.classList.remove('lazy-img');
		imgElement.classList.add('loaded');
	};
	img.onerror = () => {
		// 加载失败时使用默认图片
		imgElement.src = 'https://via.placeholder.com/220x300?text=图片加载失败';
		imgElement.classList.remove('lazy-img');
	};
	img.src = src;
};

// 加载所有图片(降级方案)
const loadAllImages = () => {
	lazyImages.value.forEach(img => {
		if (img && img.dataset.src) {
			img.src = img.dataset.src;
			img.classList.remove('lazy-img');
		}
	});
};

onMounted(() => {
	// 等待DOM更新后初始化懒加载
	setTimeout(() => {
		initLazyLoad();
	}, 300);
});

onUnmounted(() => {
	// 清理观察器
	if (observer) {
		observer.disconnect();
	}
});
</script>

<style lang="scss" scoped>
.book-item {
	position: relative;
	
	.lazy-img {
		background-color: #f8f9fa;
		background-image: linear-gradient(
			90deg,
			#f0f0f0 0%,
			#f8f8f8 50%,
			#f0f0f0 100%
		);
		background-size: 200% 100%;
		animation: shimmer 1.5s infinite;
		border-radius: 8px;
	}
	
	.lazy-img.loaded {
		animation: none;
		background: none;
	}
	
	// 优化 left-line 和 highlight,只在图片加载后显示
	.left-line, .highlight {
		opacity: 0;
		transition: opacity 0.3s;
	}
	
	.loaded ~ .left-line,
	.loaded ~ .highlight {
		opacity: 1;
	}
	
	// 图片加载动画
	.img {
		transition: opacity 0.3s ease;
		opacity: 0;
	}
	
	.img.loaded {
		opacity: 1;
	}
}

@keyframes shimmer {
	0% {
		background-position: -200% 0;
	}
	100% {
		background-position: 200% 0;
	}
}
</style>

方案3:使用 VueUse 的 useIntersectionObserver(最简洁)

首先安装 VueUse:

复制代码
npm install @vueuse/core

<template>
	<div class="book-item" v-for="item in list" :key="item.novelID">
		<div class="left-line"></div>
		<!-- 使用 ref 绑定 -->
		<img 
			:data-src="item.coverUrl" 
			:src="placeholder" 
			class="img" 
			alt=""
			ref="imageRefs"
		/>
		<div class="highlight"></div>
		<!-- ... 其他内容 ... -->
	</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { useIntersectionObserver } from '@vueuse/core';

const placeholder = ref('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjVmNWY1Ii8+PC9zdmc+');

// 图片引用数组
const imageRefs = ref([]);

// 图片懒加载逻辑
const lazyLoadImages = () => {
	imageRefs.value.forEach((img, index) => {
		if (!img || !img.dataset.src) return;
		
		const { stop } = useIntersectionObserver(
			img,
			([{ isIntersecting }]) => {
				if (isIntersecting) {
					// 图片进入视口,开始加载
					loadImage(img);
					stop(); // 停止观察
				}
			},
			{
				rootMargin: '50px',
				threshold: 0.01
			}
		);
	});
};

const loadImage = (imgElement) => {
	const src = imgElement.dataset.src;
	if (!src) return;
	
	const img = new Image();
	img.onload = () => {
		imgElement.src = src;
		imgElement.classList.add('loaded');
	};
	img.onerror = () => {
		imgElement.src = 'https://via.placeholder.com/220x300?text=图片加载失败';
		imgElement.classList.add('error');
	};
	img.src = src;
};

onMounted(() => {
	setTimeout(() => {
		lazyLoadImages();
	}, 300);
});
</script>
相关推荐
学嵌入式的小杨同学1 小时前
从零打造 Linux 终端 MP3 播放器!用 C 语言实现音乐自由
linux·c语言·开发语言·前端·vscode·ci/cd·vim
weixin_425543732 小时前
TRAE CN3.3.25 构建的Electron简易DEMO应用
前端·typescript·electron·vite·nestjs
Mr Xu_3 小时前
【Vue3 + ECharts 实战】正确使用 showLoading、resize 与 dispose 避免内存泄漏
前端·信息可视化·vue·echarts
0思必得03 小时前
[Web自动化] Selenium设置相关执行文件路径
前端·爬虫·python·selenium·自动化
雯0609~3 小时前
hiprint:实现项目部署与打印1-官网提供普通html版本
前端·html
yuezhilangniao3 小时前
AI智能体全栈开发工程化规范 备忘 ~ fastAPI+Next.js
javascript·人工智能·fastapi
不绝1914 小时前
UGUI——进阶篇
前端
~牧马~4 小时前
【记录63】electron打包vue项目之踩坑
vue.js·electron·electron与node兼容
Exquisite.4 小时前
企业高性能web服务器(4)
运维·服务器·前端·网络·mysql
铅笔侠_小龙虾4 小时前
Flutter Demo
开发语言·javascript·flutter