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>
相关推荐
光影少年3 分钟前
react navite手写 FlatList 优化配置
前端·react native·react.js
曹牧3 分钟前
C#:文本文件读取
服务器·前端·c#
柚yuzumi7 分钟前
前端优化,从少触发一次开始:防抖与节流
前端·javascript
默_笙29 分钟前
🚤 CSS 布局的"圈地运动":BFC 就是浏览器的独立领地
前端·javascript
我爱写代码i31 分钟前
AI对话绘画数字人源码 - uniapp前端
前端·人工智能·uni-app
OpenTiny社区1 小时前
太酷了!装上OpenTiny dsh‑genui这个插件,你的DeepSeek Harness点击就能干活了!
前端·ai编程
用户938515635071 小时前
大模型记忆指南:从短时缓存到永久记忆,手把手带你吃透 LangChain Memory 管理
javascript·人工智能
ModyQyW1 小时前
vite-plugin-uni-pages 更新了什么
前端·uni-app
xqchen1 小时前
Web Components 普及困境深度解析:技术标准与工程实践的落差
javascript
爱丶不疚1 小时前
写给前端工程师的现代 Python 工程化最佳实践:从 pnpm 到 uv,从 CommonJS 到 src-layout
javascript·python·typescript