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>
相关推荐
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue小型房屋租赁系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
牛马1112 小时前
Flutter 多语言
前端·flutter
by————组态2 小时前
集成详细说明
前端·物联网·信息可视化·组态·组态软件
2501_944521002 小时前
rn_for_openharmony商城项目app实战-商品评价实现
javascript·数据库·react native·react.js·ecmascript·harmonyos
我是小疯子663 小时前
jQuery快速入门指南
前端
萌萌哒草头将军3 小时前
Node.js 存在多个严重安全漏洞!官方建议尽快升级🚀🚀🚀
vue.js·react.js·node.js
程序猿的程3 小时前
我用 stock-sdk 构建了一个个人专属的 A 股行情仪表盘
javascript·web前端
傻啦嘿哟3 小时前
Python中的@property:优雅控制类成员访问的魔法
前端·数据库·python
这个图像胖嘟嘟3 小时前
前端开发的基本运行环境配置
开发语言·javascript·vue.js·react.js·typescript·npm·node.js