一般用戶打開網頁時,整個網頁的內容將被下載而且呈如今一個頁面中,雖然容許瀏覽器緩存頁面,可是不能保證用戶查看全部下載的的內容,例如一個照片牆應用,可能用戶僅僅查看第一個圖片以後離開,結果就是白白浪費了內存和帶寬。所以咱們須要當用戶須要訪問頁面的一部分時纔去加載內容,而不是一看是就去加載所有內容。node
當有人向網頁(圖像,視頻)等資源,資源引用一個小的佔位符,當用戶瀏覽網頁,實際的資源被瀏覽器緩存,而且當資源在屏幕上可見時替換佔位符,例如,若是用戶加載網頁並當即離開網頁,則除了網頁的頂部以外沒有任何內容被加載。 api
以加載圖片爲例子,咱們須要將img
標籤中設置一個data-src
屬性,它指向的是實際上咱們須要加載的圖像,而img
的src
指向一張默認的圖片,若是爲空的話也會向服務器發送請求。瀏覽器
<img src="default.jpg" data-src="www.example.com/1.jpg">
複製代碼
以後當用戶訪問的可視區域的img
元素時,將src
得值替換爲data-src
指向的實際資源加載的圖像緩存
具體代碼bash
const lazy = (el) => {
let scrTop = getTop();
let windowHeight = document.documentElement.clientHeight;
function getTop(){
return document.documentElement.scrollTop || document.body.scrollTop;
}
function getOffset(node){
return node.getBoundingClientRect().top + scrTop;
}
function inView(node){
// 設立閾值
const threshold = 0;
const viewTop = scrTop;
const viewBot = viewTop + windowHeight;
const nodeTop = getOffset(node);
const nodeBot = nodeTop + node.offsetHeight;
const offset = (threshold / 100) * windowHeight;
console.log((nodeBot >= viewTop - offset), (nodeTop <= viewBot + offset))
return (nodeBot >= viewTop - offset) && (nodeTop <= viewBot + offset)
}
function check(node){
let el = document.querySelector(node);
let images = [...el.querySelectorAll('img')];
images.forEach(img => {
if(inView(img)){
img.src = img.dataset.src;
}
})
}
check(el);
}
window.onscroll = function(){
lazy('.foo');
}
複製代碼
經過上面例子的實現,咱們要實現懶加載都須要去監聽scroll事件,儘管咱們能夠經過函數節流的方式來阻止高頻率的執行函數,可是咱們仍是須要去計算scrollTop
,offsetHeight
等屬性,有沒有簡單的不須要計算這些屬性的方式呢,答案是有的---IntersectionObserver服務器
根據MDN:app
IntersectionObserver API爲開發者提供了一種能夠異步監聽目標元素與其祖先或視窗(viewport)處於交叉狀態的方式。祖先元素與視窗(viewport)被稱爲根(root)。框架
簡單來講就是觀察一個元素和另外一個元素是否重疊。異步
IntersectionObserver初始化的過程當中提供了三個主要元素的配置:函數
root
指向的是瀏覽器的視口,但實際上能夠是任意的DOM元素,要注意的是:root
在這種狀況下,要觀察元素的必選要在root表明的Dom元素內部
margin
CSS相似,好比rootMargin: '50px 20px 10px 40px'
(top, right, bottom, left)
intersectionObserver
咱們想要得配置,咱們只須要將咱們得config
對象和咱們的回調函數一塊兒傳遞到Observer構造函數中const config = {
root: null,
rootMargin: '0px',
threshold: 0.5
}
let observer = new IntersectionObserver(fucntion(entries){
// ...
}, config)
複製代碼
如今咱們須要去給IntersectionObserver
實際觀察的元素
const img = document.querySelector('image');
observer.observe(img);
複製代碼
關於這個實際觀察的元素須要注意幾點
root
表明的DOM元素中IntersectionObserver
一次只能接受一個觀察元素,不支持批量觀察。這意味着若是你須要觀察幾個元素(好比說一個頁面上的幾個圖像),你必須遍歷全部元素並分別觀察它們中的每個const images = document.querySelecttorAll('img');
images.forEach(image => {
observer.observe(image)
})
複製代碼
IntersectionObserver回調函數
new IntersectionObserver(function(entries, self))
複製代碼
在entries
咱們獲得咱們的回調函數做爲Array是特殊類型的:IntersectionObserverEntry
首先IntersectionObserverEntry
含有三個不一樣的矩形的信息
root
+ rootMargin
)'的矩形IntersectionObserverEntry
還提供了isIntersecting
,這是一個方便的屬性,返回觀察元素是否與捕獲框架相交, 另外,IntersectionObserverEntry
提供了利於計算的遍歷屬性intersctionRatio
:返回intersectionRect 與 boundingClientRect 的比例值.
target
則返回要觀察的元素 好了簡單介紹完,讓咱們回到正題,用這個IntersectionObserver
來實現代化的懶加載方式吧
const images = document.querySelectorAll('[data-src]')
const config = {
rootMargin: '0px',
threshold: 0
};
let observer = new IntersectionObserver((entries, self)=>{
entries.forEach(entry => {
if(entry.isIntersecting){
// 加載圖像
preloadImage(entry.target);
// 解除觀察
self.unobserve(entry.target)
}
})
}, config)
images.forEach(image => {
observer.observe(image);
});
function preloadImage(img) {
const src = img.dataset.src
if (!src) { return; }
img.src = src;
}
複製代碼
相比於以前懶加載的方式是否是更加簡潔,並且只有當觀察元素和捕捉框架交叉或重疊時,纔會觸發回掉函數(加載頁面時也會觸發回調函數,不過咱們能夠用isIntersecting
來進行判斷是否和觀察元素相交)
參考:
Now You See Me: How To Defer, Lazy-Load And Act With IntersectionObserver