前端懶加載(也稱為延遲加載或按需加載)是一種網(wǎng)頁性能優(yōu)化的技術(shù),主要用于在網(wǎng)頁中延遲加載某些資源,如圖片、視頻或其他媒體文件,直到它們實(shí)際需要被用戶查看或交互時(shí)才進(jìn)行加載。這種技術(shù)特別適用于長頁面或包含大量媒體資源的頁面,因?yàn)樗梢燥@著提高頁面加載速度,減少用戶等待時(shí)間,并降低服務(wù)器負(fù)載。
懶加載的原理為基于視口(viewport)的概念,即用戶當(dāng)前在屏幕上可見的區(qū)域。當(dāng)頁面加載時(shí),只有視口內(nèi)的資源會(huì)被立即加載,而視口外的資源則會(huì)被延遲加載。當(dāng)用戶滾動(dòng)頁面或觸發(fā)其他交互行為時(shí),視口外的資源才會(huì)根據(jù)需要被加載。
懶加載的優(yōu)點(diǎn):
提高頁面加載速度:由于只加載視口內(nèi)的資源,頁面初始加載時(shí)間大大減少。
節(jié)省帶寬和服務(wù)器資源:減少不必要的資源加載,降低服務(wù)器負(fù)載和帶寬消耗。
提升用戶體驗(yàn):減少用戶等待時(shí)間,使頁面更加流暢和響應(yīng)迅速。
那么實(shí)現(xiàn)懶加載的方式有哪些呢?本文主要從原生、vue、react角度來說一下實(shí)現(xiàn)懶加載的常見方法!
loading 屬性指定瀏覽器是應(yīng)立即加載圖像還是延遲加載圖像。現(xiàn)代瀏覽器已經(jīng)開始支持img標(biāo)簽的loading屬性。設(shè)置 loading="lazy" 只有鼠標(biāo)滾動(dòng)到該圖片所在位置才會(huì)顯示。
loading的屬性值有eager和lazy,默認(rèn)值為eager,表示圖像立即加載;lazy表示圖像延遲加載,只有鼠標(biāo)滾動(dòng)到該圖片所在位置才會(huì)顯示。
<img src="/images/paris.jpeg" alt="Paris" loading="lazy">
<img src="/images/nature.jpeg" alt="Nature" loading="lazy"
這是實(shí)現(xiàn)懶加載最簡單的方法,無需額外的JavaScript代碼。
Intersection Observer API
Intersection Observer API(交叉觀察器 API)提供了一種異步檢測目標(biāo)元素與祖先元素或頂級文檔的視口相交情況變化的方法。
過去,要檢測一個(gè)元素是否可見或者兩個(gè)元素是否相交并不容易,很多解決辦法不可靠或性能很差。然而,隨著互聯(lián)網(wǎng)的發(fā)展,這種需求卻與日俱增,比如,在頁面滾動(dòng)時(shí)“懶加載”圖像或其他內(nèi)容這些情況都需要用到相交檢測。
const io = new IntersectionObserver(callback, option);
IntersectionObserver是瀏覽器原生提供的構(gòu)造函數(shù),接受兩個(gè)參數(shù):callback是可見性變化時(shí)的回調(diào)函數(shù),option是配置對象(該參數(shù)可選)。
構(gòu)造函數(shù)的返回值是一個(gè)觀察器實(shí)例。實(shí)例的observe方法可以指定觀察哪個(gè) DOM 節(jié)點(diǎn)。
// 開始觀察
io.observe(document.getElementById('example'));
// 停止觀察
io.unobserve(element);
// 關(guān)閉觀察器
io.disconnect();
上面代碼中,observe的參數(shù)是一個(gè) DOM 節(jié)點(diǎn)對象。如果要觀察多個(gè)節(jié)點(diǎn),就要多次調(diào)用這個(gè)方法。
io.observe(elementA);
io.observe(elementB);
callack參數(shù):目標(biāo)元素的可見性變化時(shí),就會(huì)調(diào)用觀察器的回調(diào)函數(shù)callback。
一般會(huì)觸發(fā)兩次:(1)目標(biāo)元素剛剛進(jìn)入視口(開始可見);(2)完全離開視口(開始不可見)。
callback函數(shù)的參數(shù)是一個(gè)數(shù)組,每個(gè)成員都是一個(gè)IntersectionObserverEntry對象。
IntersectionObserverEntry 對象:提供目標(biāo)元素的信息,一共有六個(gè)屬性。
time:可見性發(fā)生變化的時(shí)間,是一個(gè)高精度時(shí)間戳,單位為毫秒。
target:被觀察的目標(biāo)元素,是一個(gè) DOM 節(jié)點(diǎn)對象。
rootBounds:根元素的矩形區(qū)域的信息,getBoundingClientRect()方法的返回值,如果沒有根元素(即直接相對于視口滾動(dòng)),則返回null。
boundingClientRect:目標(biāo)元素的矩形區(qū)域的信息。
isIntersecting: 布爾值,目標(biāo)元素與交集觀察者的根節(jié)點(diǎn)是否相交(常用)。
intersectionRect:目標(biāo)元素與視口(或根元素)的交叉區(qū)域的信息。
intersectionRatio:目標(biāo)元素的可見比例,即intersectionRect占boundingClientRect的比例,完全可見時(shí)為1,完全不可見時(shí)小于等于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監(jiān)聽元素是否進(jìn)入可視區(qū)域
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);
});
}
});
事件監(jiān)聽+getBoundingClientReact()
舊版本的瀏覽器可能不支持Intersection ObserverAPI,此時(shí)可以用 getBoundingClientRect 和事件監(jiān)聽結(jié)合來實(shí)現(xiàn)。
Element.getBoundingClientRect() 方法返回一個(gè) DOMRect 對象,其提供了元素的大小及其相對于視口的位置。
返回值是一個(gè) DOMRect 對象,是包含整個(gè)元素的最小矩形(包括 padding 和 border-width)。該對象使用 left、top、right、bottom、x、y、width 和 height 這幾個(gè)以像素為單位的只讀屬性描述整個(gè)矩形的位置和大小。除了 width 和 height 以外的屬性是相對于視圖窗口的左上角來計(jì)算的。
const rectObject = object.getBoundingClientRect()
因此,當(dāng)rectObject.top的值處于0-視口高度,則元素處于可視區(qū)。即
getBoundingClientRect(ele).top >= 0 && getBoundingClientRect(ele).top <= offsetHeight
實(shí)現(xiàn)如下:
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) { //如果所有懶加載圖片都已加載,移除滾動(dòng)監(jiān)聽
window.removeEventListener('scroll', checkLazyLoad);
window.removeEventListener('resize', checkLazyLoad);
}
}
//監(jiān)聽事件
window.addEventListener('scroll', checkLazyLoad);
window.addEventListener('resize', checkLazyLoad);
window.addEventListener('DOMContentLoaded', checkLazyLoad);
許多第三方庫可以實(shí)現(xiàn)懶加載,例如lazysizes 和lozad.js。
以下是使用 lazysizes 庫的示例:
首先,你需要引入lazysizes 庫。
<script src="lazysizes.min.js" async=""></script>
然后修改你的<img>標(biāo)簽,增加lazyload 類和data-src 屬性來取代 src即可。
<img data-src="image.jpg" class="lazyload" alt="image" />
Iazysizes 庫會(huì)自動(dòng)處理剩下的工作。
在Vue中vue-lazyload插件用的比較多。
首先,安裝vue-lazyload:
npm install vue-lazyload--save
接著在你的Vue項(xiàng)目中引入并使用vue-lazyload:
// main.js
import Vue from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
//或者添加選項(xiàng),例如加載時(shí)的占位圖或加載失敗時(shí)的圖像
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項(xiàng)目中,你可以使用React自帶的lazy函數(shù)結(jié)合Suspense組件來實(shí)現(xiàn)圖片懶加載。
React.lazy目前僅支持默認(rèn)導(dǎo)出(default exports)如果要加載的組件使用了命名導(dǎo)出,你需要一個(gè)中間模塊來重新導(dǎo)出它為默認(rèn)模塊。
首先,你需要?jiǎng)?chuàng)建一個(gè)懶加載組件來包裝圖片:
// LazyImage.jsx
import React from'react';
const LazyImage = ({ src, alt }) => {
return(
<img src={src} alt={alt} />
);
};
export default LazyImage;
然后使用React.lazy來動(dòng)態(tài)導(dǎo)入這個(gè)組件:
// 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 組件允許你在等待加載時(shí)顯示一個(gè)加載指示器(例如上面的<div>Loading...</div>)。
注意事項(xiàng)
兼容性:不同的瀏覽器和設(shè)備對懶加載的支持程度不同,需要確保所選用的懶加載方案具有良好的兼容性。
用戶體驗(yàn):懶加載不應(yīng)該影響用戶的正常瀏覽和操作。例如,在圖片加載過程中,可以顯示占位符或加載提示,以增加用戶的等待體驗(yàn)。
SEO優(yōu)化:雖然懶加載可能會(huì)對SEO造成一定影響,但可以通過合理的策略來優(yōu)化。例如,確保關(guān)鍵內(nèi)容和鏈接在初始加載時(shí)即可被搜索引擎索引。
應(yīng)用場景
長頁面和圖片密集型網(wǎng)站:如新聞網(wǎng)站、社交媒體、電商網(wǎng)站等,這些網(wǎng)站通常包含大量的圖片和長頁面,采用懶加載可以顯著提高頁面性能和用戶體驗(yàn)。
視頻和音頻資源豐富的網(wǎng)站:如視頻分享網(wǎng)站、音樂平臺等,這些網(wǎng)站的視頻和音頻資源占用大量帶寬和服務(wù)器資源,采用懶加載可以有效降低服務(wù)器負(fù)載和帶寬消耗。
移動(dòng)設(shè)備優(yōu)化:移動(dòng)設(shè)備的網(wǎng)絡(luò)環(huán)境通常較差,采用懶加載可以減少用戶的流量消耗和等待時(shí)間,提升用戶體驗(yàn)。
總之,懶加載技術(shù)廣泛應(yīng)用于各類網(wǎng)站和應(yīng)用中,特別是長頁面、圖片密集的網(wǎng)站、電商網(wǎng)站、新聞網(wǎng)站等。在這些場景中,懶加載技術(shù)可以顯著提高頁面性能和用戶體驗(yàn)。
該文章在 2024/5/28 10:17:07 編輯過