10行程式碼實現頁面無限滾動

大勾發表於2018-03-26

背景:

由於網頁的執行都是單執行緒的,在JS執行的過程中,頁面會呈現阻塞狀態。因此,如果JS處理的資料量過大,過程複雜,可能會造成頁面的卡頓。傳統的資料展現都以分頁的形式,但是分頁的效果並不好,需要使用者手動點選下一頁,才能看到更多的內容。有很多網站使用無限分頁的模式,即網頁視窗到達內容底部就自動載入下一部分的內容...

原理:

實現無限分頁的過程大致如下:
   1 視窗滾動到底部;
   2 觸發載入,新增到現有內容的後面。
   因此,可能會出現兩種情況:
   1 當頁面的內容很少,沒有出現滾動條;觸發載入頁面事件,直到載入到滿足條件時停止載入;
   2 當頁面的內容很多,出現了滾動條
複製程式碼

先說第一種傳統的方法

需要理解的概念

scrollHeight即真實內容的高度;

clientHeight比較好理解,是視窗的高度,就是我們在瀏覽器中所能看到內容的高度;

scrollTop是視窗上面隱藏掉的部分。

10行程式碼實現頁面無限滾動

實現思路:

1 如果真實的內容比視窗高度小,則一直載入到超過視窗

2 如果超過了視窗,則判斷下面隱藏的部分的距離是否小於一定的值,如果是,則觸發載入。(即滾動到了底部)

 // 觸發條件函式
 function lowEnough(){
            var pageHeight = Math.max(document.body.scrollHeight,document.body.offsetHeight);
            var viewportHeight = window.innerHeight || 
                document.documentElement.clientHeight ||
                document.body.clientHeight || 0;
            var scrollHeight = window.pageYOffset ||
                document.documentElement.scrollTop ||
                document.body.scrollTop || 0;
            return pageHeight - viewportHeight - scrollHeight < 20;  // 通過 真實內容高度 - 視窗高度 - 上面隱藏的高度 < 20,作為載入的觸發條件
        }
複製程式碼

第二種方法:IntersectionObserver(簡單、但對瀏覽器有要求)

一、API

它的用法非常簡單。

var io = new IntersectionObserver(callback, option);
// 開始觀察
io.observe(document.getElementById('example'));

// 停止觀察
io.unobserve(element);

// 關閉觀察器
io.disconnect();
複製程式碼

上面程式碼中,IntersectionObserver是瀏覽器原生提供的建構函式,接受兩個引數:callback是可見性變化時的回撥函式,option是配置物件(該引數可選)。

建構函式的返回值是一個觀察器例項。例項的observe方法可以指定觀察哪個 DOM 節點。

二、callback 引數

目標元素的可見性變化時,就會呼叫觀察器的回撥函式callback。

callback一般會觸發兩次。一次是目標元素剛剛進入視口(開始可見),另一次是完全離開視口(開始不可見)。

var io = new IntersectionObserver(
  entries => {
    console.log(entries);
  }
);
複製程式碼

上面程式碼中,回撥函式採用的是箭頭函式的寫法。callback函式的引數(entries)是一個陣列,每個成員都是一個IntersectionObserverEntry物件。舉例來說,如果同時有兩個被觀察的物件的可見性發生變化,entries陣列就會有兩個成員。

三、IntersectionObserverEntry 物件

IntersectionObserverEntry物件提供目標元素的資訊,一共有六個屬性。

10行程式碼實現頁面無限滾動

每個屬性的含義如下。

time:可見性發生變化的時間,是一個高精度時間戳,單位為毫秒
target:被觀察的目標元素,是一個 DOM 節點物件
rootBounds:根元素的矩形區域的資訊,getBoundingClientRect()方法的返回值,如果沒有根元素(即直接相對於視口滾動),則返回null
boundingClientRect:目標元素的矩形區域的資訊
intersectionRect:目標元素與視口(或根元素)的交叉區域的資訊
intersectionRatio:目標元素的可見比例,即intersectionRect佔boundingClientRect的比例,完全可見時為1,完全不可見時小於等於0
複製程式碼

10行程式碼實現頁面無限滾動

上圖中,灰色的水平方框代表視口,深紅色的區域代表四個被觀察的目標元素。它們各自的intersectionRatio圖中都已經註明。

例項:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .img-area{width: 500px;height: 500px; margin: 0 auto;}
        .my-photo{width:500px; height: 300px}
    </style>
</head>
<body>
<div id="container">
    <div class="img-area"><img class="my-photo" alt="loading" src="./img/1.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" src="./img/2.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" src="./img/3.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" src="./img/4.jpg" /></div>
</div>
<div class="scrollerFooter1">
    沒有內容了
</div>
<script>
    function infinityScroll(footerNode, callback) {
        var observer = new IntersectionObserver(function(changes) {
        
            // 注意intersectionRatio這個屬性值的判斷
            if (changes[0].intersectionRatio <= 0) return;
            
            callback();
            
        });
        observer.observe(document.querySelector(footerNode));
    }
    infinityScroll('.scrollerFooter1', function() {
        for (var i = 0; i< 3; i++) {
            document.getElementById('container').appendChild(document.getElementById('container').firstChild)
        }
    });
</script>
</body>
</html>
複製程式碼

當然也可以實現懶載入:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .img-area{width: 500px;height: 500px; margin: 0 auto;}
        .my-photo{width:500px; height: 300px}
    </style>
</head>
<body>
<div class="container">
    <div class="img-area"><img class="my-photo" alt="loading" data-src="./img/1.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" data-src="./img/2.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" data-src="./img/3.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" data-src="./img/4.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" data-src="./img/5.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" data-src="./img/1.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" data-src="./img/2.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" data-src="./img/3.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" data-src="./img/4.jpg" /></div>
    <div class="img-area"><img class="my-photo" alt="loading" data-src="./img/5.jpg" /></div>
</div>
<script>
    function lazyLoad(imgClassName) {
        const imgList = Array.from(document.querySelectorAll(imgClassName));
        var io = new IntersectionObserver(function (ioes) {
            ioes.forEach(function (ioe) {
                var el = ioe.target;
                var intersectionRatio = ioe.intersectionRatio;
                if (intersectionRatio > 0 && intersectionRatio <= 1) {
                    if (!el.src) {
                        el.src = el.dataset.src
                    }
                }
            })
        });
        imgList.forEach(function(item) {
            io.observe(item)
        });
    }
    lazyLoad('.my-photo');
</script>
</body>
</html>
複製程式碼

最後說一下各個瀏覽器對這個API的支援,使用前需謹慎哦:

10行程式碼實現頁面無限滾動

另外文中部分文字借鑑了大神——阮一峰的文章

相關文章