在 JavaScript 中,懒加载(Lazy Loading)主要用于延迟加载资源,例如图片、视频、音频、脚本等,直到它们真正需要时才加载。这样可以提高页面的加载速度和性能。
以下是几种常见的 JavaScript 懒加载实现方式:
1. 监听滚动事件
通过监听滚动事件来实现图片懒加载是一种传统并且常见的方法,但这种方法在性能上不是很好。
1. 标记页面上哪些图片需要懒加载,使用自定义属性。
<img class="lazy" data-src="real-image.jpg" src="placeholder.jpg" alt="Lazy loaded image">
2.监听滚动事件
window.addEventListener('scroll', lazyLoad);
3. 确定哪些元素已经滚动到了视口内或即将进入视口,需要被加载。
function lazyLoad() {const lazyImages = [...document.querySelectorAll('.lazy')].filter(isImageInView); lazyImages.forEach(loadImage);
} function isImageInView(img) { const rect = img.getBoundingClientRect(); return rect.top < window.innerHeight + window.pageYOffset;
}function loadImage(img) { img.src = img.dataset.src; // 使用data-src属性中的真实图片地址 img.classList.remove('lazy'); // 移除懒加载类,避免重复加载
}
4. 注意一点,在页面刚加载时,可能有一些元素已经出在视口内,需要立即加载这些元素,而不仅仅是等待滚动事件。因此在文档加载完成后,立即调用一次 lazyLoad 函数来实现。
document.addEventListener('DOMContentLoaded', function() { lazyLoad();
});
由于滚动事件可能非常频繁地触发,因此直接在滚动事件处理函数中执行大量操作可能会导致性能问题。有几种常见的优化方法:函数防抖、函数节流、Intersection Observer API。
我有写过函数防抖和函数节流的文档,忘记的小伙伴可以去看看哦。
2. 交叉观察器
Intersection Observer API 是一种异步观察目标元素与其祖先元素或顶级文档视口的交叉状态的方法。这个 API 提供了一种有效的方式来了解元素何时进入或离开视口(viewport),而无需进行轮询或监听滚动/调整大小事件。这是一个更现代且性能更好的解决方案。
2.1 API 介绍
var io = new IntersectionObserver(callback, option);
IntersectionObserver 是浏览器原生提供的构造函数,接受两个参数:callback 是可见变化时执行的函数,option 时配置对象(可选)。构造函数的返回值是一个观察器实例。实例的 observe 方法可以指定观察哪个 DOM 节点。
// 开始观察
io.observe(document.getElementById('example'));// 停止观察
io.unobserve(element);// 关闭观察器
io.disconnect();
callback 一般会触发两次。一次是目标元素刚进入视口,另一次是完全离开视口。
函数的参数 entries 是一个数组,每个成员都是一个 IntersectionObserverEntry 对象。如果同时有两个被观察对象的可见性发生变化,entries 数组就会有两个成员。
var io = new IntersectionObserver(entries => {console.log(entries);}
);
IntersectionObserverEntry 对象提供目标元素的信息,一共有六个属性。
{time: 可见性发生变化的时间,是一个高精度时间戳,单位为毫秒,rootBounds: 根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回 null,boundingClientRect: 目标元素的矩形区域的信息,intersectionRect: 目标元素与视口(或根元素)的交叉区域的信息,intersectionRatio: 目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0,target: 被观察的目标元素,是一个 DOM 节点对象
}
option 是配置对象,有三个属性。
{threshold: 决定何时触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0]。root: 指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点rootMargin: 定义根元素的 margin,用来扩展或缩小 rootBounds 这个矩形的大小,从而影响交叉区域的大小。它使用 CSS 的定义方法,比如10px 20px 30px 40px,表示 top、right、bottom 和 left 四个方向的值。
}
注意:
IntersectionObserver API是异步的,不会阻塞主线程,因此提高了性能。
不随目标元素的滚动同步触发。即只有线程空闲下来,才会执行观察器。意味着这个观察器的优先级非常低,只在其他任务执行完,浏览器有了空闲才会执行。
2.2 实现图片懒加载
1. 标记需要懒加载的图片
<img class="lazy" data-src="real-image.jpg" src="placeholder.jpg" alt="Lazy loaded image">
2. 初始化 IntersectionObserve
// 回调函数,当元素进入视口时执行
function handleIntersect(entries, observer) { entries.forEach(entry => { if (entry.isIntersecting) { // 获取图片元素 const lazyImage = entry.target; // 加载真实图片 lazyImage.src = lazyImage.dataset.src; // 移除data-src属性,防止重复加载 delete lazyImage.dataset.src; // 停止观察该元素 observer.unobserve(lazyImage); } });
} // 创建IntersectionObserver实例
const options = { root: null, // 使用视口作为根 rootMargin: '0px', // 根边界与目标元素的边界之间的区域 threshold: 0.1 // 当目标元素的可见比例达到这个阈值时,触发回调函数
};
const observer = new IntersectionObserver(handleIntersect, options);
3. 观察懒加载图片
// 获取所有需要懒加载的图片
const lazyImages = document.querySelectorAll('.lazy'); // 观察这些图片
lazyImages.forEach(image => { observer.observe(image);
});
4. 处理初始加载
在页面加载时,可能有一些图片已经在视口内。为了确保这些图片也被加载,需要在文档加载完成后立即检查它们的状态。
document.addEventListener('DOMContentLoaded', (event) => { // 触发一次检查,确保视口内的图片被加载 lazyImages.forEach(image => { if (image.getBoundingClientRect().top < window.innerHeight) { handleIntersect([{ isIntersecting: true, target: image }], observer); } });
});
3. 动态导入
在 JavaScript 中,动态导入(Dynamic Imports)是通过 import() 函数实现的,它允许在运行时按需加载和执行 JavaScript 模块。这种机制特别适用于实现懒加载(lazy loading),因为可以根据需要加载和执行特定的代码块,而不是一次性加载整个应用程序的所有代码。
举个例子 🌰 :
async function loadComponent() { try { const { default: MyComponent } = await import('./MyComponent.js'); // 使用 MyComponent // ... } catch (error) { // 处理加载错误 console.error('Error loading component:', error); }
} // 在需要的时候调用 loadComponent 函数
loadComponent();
4. 第三方库
还可以使用许多第三方库来实现懒加载,比如lazysizes、vue-lazyload(Vue.js的懒加载库)等。这些库通常提供了更丰富的功能和更好的性能优化。这里就不多赘述啦。
最后还有一点:
1. 在实现懒加载时,尽量不要破坏页面等语义化结构,比如,不要使用 background-image 代替 <img> 标签。
2. 懒加载不仅限于图片和脚本,还可以应用到其他类型的资源,如 CSS 文件、字体文件等。
3. 对于图片懒加载,最好使用 srcset 和 sizes 属性来提供不同分辨率的图片,以便在不同设备和屏幕尺寸上获得最佳效果。