滚动翻页时JS懒加载图片失败原因及解决方案
在前端开发中,图片懒加载是优化页面性能、减少初始资源加载压力的常用手段。不少开发者在实现滚动翻页场景下的懒加载功能时,会遇到图片无法正常加载的问题,本文将从常见原因出发,结合实际代码示例逐一分析并提供对应解决方案。
一、基础懒加载实现原理回顾
常见的图片懒加载核心逻辑是:先将图片的真实地址存放在自定义属性(如data-src)中,初始时src属性为空或占位图,当页面滚动到图片进入可视区域时,再将data-src的值赋值给src,触发图片加载。基础实现示例代码如下:
// 基础懒加载实现
function lazyLoadImages() {
// 获取所有需要懒加载的图片
const lazyImages = document.querySelectorAll('img[data-src]');
// 获取可视区域高度
const windowHeight = window.innerHeight;
// 获取当前滚动距离
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
lazyImages.forEach(img => {
// 获取图片距离页面顶部的距离
const imgTop = img.offsetTop;
// 判断图片是否进入可视区域(预留100px提前加载空间)
if (imgTop < windowHeight + scrollTop + 100) {
// 将data-src的值赋给src,触发加载
img.src = img.getAttribute('data-src');
// 加载完成后移除data-src属性,避免重复处理
img.removeAttribute('data-src');
}
});
}
// 监听滚动事件
window.addEventListener('scroll', lazyLoadImages);
// 初始加载时执行一次
lazyLoadImages();二、滚动翻页场景下懒加载失败的常见原因
1. 滚动事件未正确绑定到翻页容器
很多滚动翻页场景并非基于window滚动,而是使用自定义容器(如div)设置overflow-y: auto实现内容滚动。如果仍然监听window的滚动事件,会导致无法正确获取容器的滚动状态和可视区域范围,懒加载判断失效。
错误示例:假设翻页容器是id为scroll-container的div,错误监听如下:
// 错误:仍然监听window滚动,实际滚动的是#scroll-container
window.addEventListener('scroll', lazyLoadImages);正确做法应该是监听翻页容器的滚动事件,同时计算可视区域时需要基于容器的属性:
// 获取翻页容器
const scrollContainer = document.getElementById('scroll-container');
function lazyLoadImagesInContainer() {
// 容器可视区域高度
const containerHeight = scrollContainer.clientHeight;
// 容器滚动距离
const containerScrollTop = scrollContainer.scrollTop;
// 获取容器内所有待加载图片
const lazyImages = scrollContainer.querySelectorAll('img[data-src]');
lazyImages.forEach(img => {
// 计算图片相对于容器顶部的距离
const imgTop = img.offsetTop - scrollContainer.offsetTop;
if (imgTop < containerHeight + containerScrollTop + 100) {
img.src = img.getAttribute('data-src');
img.removeAttribute('data-src');
}
});
}
// 监听容器滚动事件
scrollContainer.addEventListener('scroll', lazyLoadImagesInContainer);
// 初始执行一次
lazyLoadImagesInContainer();2. 翻页新增内容未重新查询待加载图片
滚动翻页通常是动态加载新内容并插入到页面中,如果懒加载的逻辑只在初始时执行一次,或者每次滚动时只查询初始存在的img[data-src]元素,那么新插入的翻页内容中的待加载图片不会被纳入判断范围,自然无法触发加载。
错误示例:每次滚动都只查询初始存在的图片,不会处理新增内容:
// 初始查询一次,后续滚动不会重新获取新增的待加载图片
const lazyImages = document.querySelectorAll('img[data-src]');
function lazyLoadImages() {
const windowHeight = window.innerHeight;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
// 遍历的是初始查询的lazyImages,新增图片不在其中
lazyImages.forEach(img => {
const imgTop = img.offsetTop;
if (imgTop < windowHeight + scrollTop + 100) {
img.src = img.getAttribute('data-src');
img.removeAttribute('data-src');
}
});
}
window.addEventListener('scroll', lazyLoadImages);正确做法是在每次滚动触发懒加载时,重新查询当前页面中所有仍带有data-src属性的图片,确保新增内容被覆盖:
function lazyLoadImages() {
const windowHeight = window.innerHeight;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
// 每次执行时重新查询所有待加载图片
const lazyImages = document.querySelectorAll('img[data-src]');
lazyImages.forEach(img => {
const imgTop = img.offsetTop;
if (imgTop < windowHeight + scrollTop + 100) {
img.src = img.getAttribute('data-src');
img.removeAttribute('data-src');
}
});
}
window.addEventListener('scroll', lazyLoadImages);3. 图片位置计算错误
懒加载的核心判断是图片是否进入可视区域,如果计算图片位置和可视区域范围的逻辑有误,就会导致判断失效。常见计算错误包括:
使用
offsetTop时,没有考虑父元素的偏移量,尤其是当图片位于嵌套布局中时,offsetTop是相对于最近定位父元素的距离,而非页面顶部。翻页容器的滚动距离和可视区域高度计算混用了
window和容器的属性,比如用了容器的clientHeight却用了window.pageYOffset作为滚动距离。没有预留提前加载的缓冲空间,图片刚进入可视区域边缘才触发加载,用户可能已经滚动过去,显得加载失败。
建议使用getBoundingClientRect方法获取元素相对于可视区域的位置,该方法返回的属性不受父元素布局影响,计算更准确:
function lazyLoadImages() {
const lazyImages = document.querySelectorAll('img[data-src]');
lazyImages.forEach(img => {
// 获取图片相对于可视区域的位置信息
const rect = img.getBoundingClientRect();
// rect.top是图片顶部到可视区域顶部的距离,rect.bottom是图片底部到可视区域顶部的距离
// 当图片顶部小于可视区域高度(进入可视区域),且图片底部大于0(没有完全滚出可视区域)时触发加载
if (rect.top < window.innerHeight && rect.bottom > 0) {
img.src = img.getAttribute('data-src');
img.removeAttribute('data-src');
}
});
}
window.addEventListener('scroll', lazyLoadImages);4. 翻页请求未完成时滚动触发懒加载
滚动翻页的逻辑通常是:滚动到底部时触发接口请求,获取新一页的数据,然后将新内容插入页面。如果新内容插入是异步的,在插入完成前,懒加载逻辑已经执行,此时新内容的图片还不存在于DOM中,自然无法被处理。或者翻页请求失败时,没有新内容插入,但懒加载逻辑仍然执行,不会出现新的加载行为,被误认为加载失败。
解决方案是确保在翻页内容插入DOM后再执行一次懒加载逻辑,同时处理请求失败的情况:
// 翻页加载函数
async function loadNextPage() {
// 避免重复请求
if (window.isLoading) return;
window.isLoading = true;
try {
// 模拟请求翻页数据,实际项目中替换为真实接口地址 https://www.ipipp.com/api/list
const response = await fetch('https://www.ipipp.com/api/list?page=' + window.currentPage);
const data = await response.json();
if (data.list.length > 0) {
// 将新内容插入容器
const container = document.getElementById('content-container');
data.list.forEach(item => {
const img = document.createElement('img');
img.setAttribute('data-src', item.imgUrl);
img.alt = item.title;
container.appendChild(img);
});
// 新内容插入后执行一次懒加载,处理新增图片
lazyLoadImages();
window.currentPage++;
}
} catch (error) {
console.error('翻页请求失败:', error);
} finally {
window.isLoading = false;
}
}
// 监听滚动到底部触发翻页
window.addEventListener('scroll', () => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
// 滚动到距离底部100px时触发翻页
if (scrollTop + windowHeight >= documentHeight - 100) {
loadNextPage();
}
// 同时执行懒加载
lazyLoadImages();
});5. 图片真实地址属性或赋值逻辑错误
部分场景中,开发者可能会用不同的自定义属性存储图片真实地址,比如data-original、lazy-src等,如果懒加载逻辑中读取的属性名和实际存储的属性名不一致,就无法正确获取地址,导致src赋值失败。另外,如果赋值后没有移除自定义属性,会导致每次滚动都重复赋值,虽然不影响加载,但可能造成性能问题,不过如果判断逻辑有问题,也可能出现异常。
建议统一自定义属性名称,比如统一使用data-src,并在赋值后移除该属性,避免重复处理:
function lazyLoadImages() {
const lazyImages = document.querySelectorAll('img[data-src]');
lazyImages.forEach(img => {
const rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight && rect.bottom > 0) {
// 读取data-src属性
const realSrc = img.getAttribute('data-src');
if (realSrc) {
img.src = realSrc;
// 赋值完成后移除data-src,避免下次重复处理
img.removeAttribute('data-src');
}
}
});
}三、其他注意事项
除了上述常见原因,还有一些细节可能导致懒加载失败:
图片初始样式设置为
display: none,此时getBoundingClientRect返回的top、bottom等属性会异常,需要确保在图片进入可视区域前将其显示,或者调整判断逻辑。频繁的滚动事件触发会导致性能问题,建议使用节流函数优化滚动事件的处理,避免每次滚动都执行大量查询和计算:
// 节流函数,限制函数在一定时间内只执行一次
function throttle(fn, delay = 200) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}
// 使用节流后的懒加载函数
window.addEventListener('scroll', throttle(lazyLoadImages, 200));如果项目中使用了第三方懒加载库,还需要检查库的配置是否符合滚动翻页的场景,比如是否支持动态内容、是否正确配置了滚动容器等,必要时可以查看库的文档或源码排查问题。