前端懒加载(也称为延迟加载或按需加载)是一种网页性能优化的技术,主要用于在网页中延迟加载某些资源,如图片、视频或其他媒体文件,直到它们实际需要被用户查看或交互时才进行加载。这种技术特别适用于长页面或包含大量媒体资源的页面,因为它可以显著提高页面加载速度,减少用户等待时间,并降低服务器负载。
懒加载的原理为基于视口(viewport)的概念,即用户当前在屏幕上可见的区域。当页面加载时,只有视口内的资源会被立即加载,而视口外的资源则会被延迟加载。当用户滚动页面或触发其他交互行为时,视口外的资源才会根据需要被加载。
懒加载的优点:
- 提高页面加载速度:由于只加载视口内的资源,页面初始加载时间大大减少。
- 节省带宽和服务器资源:减少不必要的资源加载,降低服务器负载和带宽消耗。
- 提升用户体验:减少用户等待时间,使页面更加流畅和响应迅速。
那么实现懒加载的方式有哪些呢?本文主要从原生、vue、react角度来说一下实现懒加载的常见方法!
一、原生的loading属性
loading 属性指定浏览器是应立即加载图像还是延迟加载图像。现代浏览器已经开始支持img标签的loading属性。设置 loading=“lazy” 只有鼠标滚动到该图片所在位置才会显示。
loading的属性值有eager和lazy,默认值为eager,表示图像立即加载;lazy表示图像延迟加载,只有鼠标滚动到该图片所在位置才会显示。
<img src="/images/paris.jpeg" alt="Paris" loading="lazy">
<img src="/images/nature.jpeg" alt="Nature" loading="lazy">
这是实现懒加载最简单的方法,无需额外的JavaScript代码。
二、Intersection Observer API
Intersection Observer API(交叉观察器 API)提供了一种异步检测目标元素与祖先元素或顶级文档的视口相交情况变化的方法。
过去,要检测一个元素是否可见或者两个元素是否相交并不容易,很多解决办法不可靠或性能很差。然而,随着互联网的发展,这种需求却与日俱增,比如,在页面滚动时“懒加载”图像或其他内容这些情况都需要用到相交检测。
const io = new IntersectionObserver(callback, option);
IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。
构造函数的返回值是一个观察器实例。实例的observe方法可以指定观察哪个 DOM 节点。
// 开始观察
io.observe(document.getElementById('example'));// 停止观察
io.unobserve(element);// 关闭观察器
io.disconnect();
上面代码中,observe的参数是一个 DOM 节点对象。如果要观察多个节点,就要多次调用这个方法。
io.observe(elementA);
io.observe(elementB);
**callack参数:**目标元素的可见性变化时,就会调用观察器的回调函数callback。
一般会触发两次:(1)目标元素刚刚进入视口(开始可见);(2)完全离开视口(开始不可见)。
callback函数的参数是一个数组,每个成员都是一个IntersectionObserverEntry对象。
**IntersectionObserverEntry 对象:**提供目标元素的信息,一共有六个属性。
- time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒。
- target:被观察的目标元素,是一个 DOM 节点对象。
- rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null。
- boundingClientRect:目标元素的矩形区域的信息。
- isIntersecting: 布尔值,目标元素与交集观察者的根节点是否相交(常用)。
- intersectionRect:目标元素与视口(或根元素)的交叉区域的信息。
- intersectionRatio:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0。
所以可以通过判断isIntersecting属性是否为true来判断元素的可见性。
对于需要懒加载的图片,使用data-src代替src。
<img data-src="image.jpg" alt="description">
document.addEventListener("DOMContentLoaded", function() {const images = document.querySelectorAll('img[data-src]');const loadImage = function(image) {image.setAttribute('src', image.getAttribute('data-src'));image.onload = function() {image.removeAttribute('data-src');}}//使用IntersectionObserver监听元素是否进入可视区域if ('IntersectionObserver' in window) {var observer = new IntersectionObserver(function(items, observer) {items.forEach(function(item) {if (item.isIntersecting) {loadImage(item.target);observer.unobserve(item.target);}});}, {threshold: 0.01});images.forEach(function(img) {observer.observe(img);});} else {//不支持IntersectionObserver,直接加载所有图片images.forEach(function(img) {loadImage(img);});}
});
四、事件监听+getBoundingClientReact()
旧版本的浏览器可能不支持Intersection ObserverAPI,此时可以用 getBoundingClientRect 和事件监听结合来实现。
Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置。
返回值是一个 DOMRect 对象,是包含整个元素的最小矩形(包括 padding 和 border-width)。该对象使用 left、top、right、bottom、x、y、width 和 height 这几个以像素为单位的只读属性描述整个矩形的位置和大小。除了 width 和 height 以外的属性是相对于视图窗口的左上角来计算的。
const rectObject = object.getBoundingClientRect()
因此,当rectObject.top的值处于0-视口高度,则元素处于可视区。即
getBoundingClientRect(ele).top >= 0 && getBoundingClientRect(ele).top <= offsetHeight
实现如下:
function isElementInviewport(el) {const rect = el.getBoundingClientRect();return (rect.bottom >= 0 &&rect.right >= 0 &&rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerwidth || document.documentElement.clientWidth);)
}function checkLazyLoad() {const lazyImages = Array.prototype.slice.call(document.querySelectorAll('img[data-src]'));lazyImages.forEach(function(img) {if (isElementInViewport(img)) {img.src = img.getAttribute('data-src');img.removeAttribute('data-src');}});if (lazyImages.length === 0) { //如果所有懒加载图片都已加载,移除滚动监听window.removeEventListener('scroll', checkLazyLoad);window.removeEventListener('resize', checkLazyLoad);}
}//监听事件
window.addEventListener('scroll', checkLazyLoad);
window.addEventListener('resize', checkLazyLoad);
window.addEventListener('DOMContentLoaded', checkLazyLoad);
五、使用第三方库
许多第三方库可以实现懒加载,例如lazysizes 和lozad.js。
以下是使用 lazysizes 库的示例:
首先,你需要引入lazysizes 库。
<script src="lazysizes.min.js" async=""></script>
然后修改你的标签,增加lazyload 类和data-src 属性来取代 src即可。
<img data-src="image.jpg" class="lazyload" alt="image" />
Iazysizes 库会自动处理剩下的工作。
六、vue中使用vue-lazyload
在Vue中vue-lazyload插件用的比较多。
首先,安装vue-lazyload:
npm install vue-lazyload--save
接着在你的Vue项目中引入并使用vue-lazyload:
// main.js
import Vue from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
//或者添加选项,例如加载时的占位图或加载失败时的图像
Vue.use(VueLazyload, {preLoad: 1.3,error: 'error.png',loading: 'loading.gif',attempt: 1
})
new Vue({render: h => h(App)
}).$mount('#app')
然后在组件中使用v-lazy指令来替换src属性:
<img v-lazy="imgUrl" alt="image">
七、React中的lazy函数
React项目中,你可以使用React自带的lazy函数结合Suspense组件来实现图片懒加载。
React.lazy目前仅支持默认导出(default exports)如果要加载的组件使用了命名导出,你需要一个中间模块来重新导出它为默认模块。
首先,你需要创建一个懒加载组件来包装图片:
// LazyImage.jsx
import React from'react';
const LazyImage = ({ src, alt }) => {return(<img src={src} alt={alt} />);
};
export default LazyImage;
然后使用React.lazy来动态导入这个组件:
// App.js 或者其他父组件
import React, { Suspense } from 'react';
const LazyImage = React.lazy(() => import('./LazyImage'));
// 懒加载组件
const App = () =>{return (<div><Suspense fallback={<div>Loading...</div>}>{/* 在Suspense组件中渲染懒加载的组件 */}<LazyImage src="image.jpg" alt="图片描述"/></Suspense></div>);
};
export default App;
Suspense 组件允许你在等待加载时显示一个加载指示器(例如上面的
总结
注意事项
- 兼容性:不同的浏览器和设备对懒加载的支持程度不同,需要确保所选用的懒加载方案具有良好的兼容性。
- 用户体验:懒加载不应该影响用户的正常浏览和操作。例如,在图片加载过程中,可以显示占位符或加载提示,以增加用户的等待体验。
- SEO优化:虽然懒加载可能会对SEO造成一定影响,但可以通过合理的策略来优化。例如,确保关键内容和链接在初始加载时即可被搜索引擎索引。
应用场景
- 长页面和图片密集型网站:如新闻网站、社交媒体、电商网站等,这些网站通常包含大量的图片和长页面,采用懒加载可以显著提高页面性能和用户体验。
- 视频和音频资源丰富的网站:如视频分享网站、音乐平台等,这些网站的视频和音频资源占用大量带宽和服务器资源,采用懒加载可以有效降低服务器负载和带宽消耗。
- 移动设备优化:移动设备的网络环境通常较差,采用懒加载可以减少用户的流量消耗和等待时间,提升用户体验。
总之,懒加载技术广泛应用于各类网站和应用中,特别是长页面、图片密集的网站、电商网站、新闻网站等。在这些场景中,懒加载技术可以显著提高页面性能和用户体验。