如何判斷元素是否進入可視區域viewport?

付出發表於2019-03-03

個性簽名: 生如夏花,逝如冬雪;人生如此,何悔何怨。

前言: 經常需要計算元素的大小或者所在頁面的位置,offsetWidth,clientWidth,scrollWidth,scrollTop這幾個關鍵字的出現更是家常便飯,每次碰到都需要事先實驗一番。為了下次開發提高效率。在這裡一次性做個總結,以用來判斷元素是否在可視區域以及用原生js簡單實現懶載入。文末有個簡單的懶載入實現的demo,有需要的可以看一下。

目錄

如何判斷元素是否進入可視區域viewport?

工欲善其事,必先利其器。在判斷元素是否在可視區域實現簡單的原生懶載入前,我們先簡單回顧下以下幾個關鍵的概念。

ps: 如果你對這些概念已經比較熟悉了,可以直接跳到第五點檢視關鍵程式碼示例。

1. 偏移量

偏移量(offset dimension),元素的可見大小由其高度、寬度決定,包括所有內邊距、滾動條和邊框大小(注意,不包括外邊距)。通過下列4個屬性可以取得元素的偏移量。

偏移量 概念 公式
offsetHeight 元素在垂直方向上佔用的空間大小,以畫素計。包括元素的高度、(可見的) 水平滾動條的高度、上邊框高度和下邊框高度。 offsetHeght = content + padding + border + scrollX
offsetWidth 元素在水平方向上佔用的空間大小,以畫素計。包括元素的寬度、(可見的)垂 直滾動條的寬度、左邊框寬度和右邊框寬度。 offsetWidth = content + padding + border + scrollY
offsetLeft 元素的左外邊框至**包含元素的左內邊框之間的畫素距離。
offsetTop 元素的上外邊框至包含元素的上內邊框之間的畫素距離。

其中,offsetLeft 和 offsetTop 屬性與包含元素有關,包含元素的引用儲存在 offsetParent 屬性中。offsetParent 屬性不一定與 parentNode 的值相等。

如下圖顯示

偏移量圖示

注意: 所有這些偏移量屬性都是隻讀的,而且每次訪問它們都需要重新計算。因此,應該儘量避免重複訪問這些屬性;如果需要重複使用其中某些屬性的值,可以將它們保 存在區域性變數中,以提高效能。

這也是上篇文章文字跑馬燈專案中(戳此跳轉),為什麼增加padding後,textWidth需要重新獲取的原因

文字跑馬燈

小結

偏移量: 只讀屬性;包括滾動條和邊框,不包括外邊距。

2. 客戶區大小

客戶區大小是隻讀的,每次訪問都要重新計算的。

客戶區大小 概念 公式
clientWidth clientWidth 屬性是元素內容區寬度加 上左右內邊距寬度; clientWidth = content + padding
clientHeight 元素內容區高度加上上下內邊距高度 clientHeight = content + padding

最常用到這些屬性的情況,就是確定瀏覽器視口大小的時候(在 IE7 之前的版本中)。如下面的例子所示:

function getViewport(){
    // 檢查 document.compatMode 屬性,以確定瀏覽器是否執行在混雜模式。
    // Safari3.1 之前的版本不支援這個屬性,因此就會自動執行 else 語句
    if (document.compatMode == "BackCompat"){
        return {
            width: document.body.clientWidth,
            height: document.body.clientHeight
        };
    } else {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight
        }; 
    }
}
複製程式碼

小結

客戶區大小: 只讀屬性;不包括滾動條和邊框,不包括外邊距。

3. 滾動大小

概念
scrollHeight 在沒有滾動條的情況下,元素內容的總高度。
scrollWidth 在沒有滾動條的情況下,元素內容的總寬度。
scrollLeft 被隱藏在內容區域左側的畫素數。通過設定這個屬性可以改變元素的滾動位置。
scrollTop 被隱藏在內容區域上方的畫素數。通過設定這個屬性可以改變元素的滾動位置。

scrollWidth 和 scrollHeight 主要用於確定元素內容的實際大小。

scrollLeft 和 scrollTop屬性既可以確定元素當前滾動的狀態,也可以設定元素的滾動位 置。在元素尚未被滾動時,這兩個屬性的值都等於 0。如果元素被垂直滾動了,那麼 scrollTop 的值 會大於 0,且表示元素上方不可見內容的畫素高度。如果元素被水平滾動了,那麼 scrollLeft 的值會 大於 0,且表示元素左側不可見內容的畫素寬度。這兩個屬性都是可以設定的,因此將元素的 scrollLeft 和 scrollTop 設定為 0,就可以重置元素的滾動位置。比如:上篇文章文字跑馬燈專案中scrollLeft的使用(戳此跳轉)

小結

只讀屬性,不包括滾動條、border。

4. 確定元素大小

getBoundingClientRect

getBoundingClientRect的相容性寫法:

對於不支援 getBoundingClientRect()的瀏覽器,可以通過其他手段取得相同的資訊。一般來 說,right 和 left 的差值與 offsetWidth 的值相等,而 bottom 和 top 的差值與 offsetHeight 相等。綜合上述,就可以建立出下面這個跨瀏覽器的函式:


function getElementLeft(element){
    var actualLeft = element.offsetLeft;
    var current = element.offsetParent;
    while (current !== null){
        actualLeft += current.offsetLeft;
        current = current.offsetParent;
    }
    return actualLeft;
}

function getElementTop(element){
    var actualTop = element.offsetTop;
    var current = element.offsetParent;
    while (current !== null){
        actualTop += current. offsetTop;
        current = current.offsetParent;
    }
    return actualTop;
}

function getBoundingClientRect(element) {
    var scrollTop = document.documentElement.scrollTop;
    var scrollLeft = document.documentElement.scrollLeft;
    if (element.getBoundingClientRect) {
        if (typeof arguments.callee.offset != "number") {
            var temp = document.createElement("div");
            temp.style.cssText = "position:absolute;left:0;top:0;"; document.body.appendChild(temp);
            arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop; document.body.removeChild(temp);
            temp = null;
        }
        var rect = element.getBoundingClientRect();
        var offset = arguments.callee.offset;
        return {
            left: rect.left + offset,
            right: rect.right + offset,
            top: rect.top + offset,
            bottom: rect.bottom + offset
        };
    } else {
        var actualLeft = getElementLeft(element);
        var actualTop = getElementTop(element);
        return {
            left: actualLeft - scrollLeft,
            right: actualLeft + element.offsetWidth - scrollLeft,
            top: actualTop - scrollTop,
            bottom: actualTop + element.offsetHeight - scrollTop
        }
    }
}
複製程式碼

5.判斷元素是否在可視區域

知道了元素的大小以及所位於的區域外,我們可以做些什麼呢?我們可以通過上面學到的知識點來檢測元素是否在可視區域,再說大一點,這也是懶載入圖片的實現原理。

5.1 第一種方法

公式: el.offsetTop - document.documentElement.scrollTop <= viewPortHeight

function isInViewPortOfOne (el) {
    // viewPortHeight 相容所有瀏覽器寫法
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
    const offsetTop = el.offsetTop
    const scrollTop = document.documentElement.scrollTop
    const top = offsetTop - scrollTop
    console.log('top', top)
     // 這裡有個+100是為了提前載入+ 100
    return top <= viewPortHeight + 100
}
複製程式碼

5.2 第二種方法

公式: el.getBoundingClientReact().top <= viewPortHeight

其實, el.offsetTop - document.documentElement.scrollTop = el.getBoundingClientRect().top, 利用這點,我們可以用下面程式碼代替方法一

function isInViewPortOfTwo (el) {
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
    const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
    console.log('top', top)
    return top  <= viewPortHeight + 100
}
複製程式碼

5.3 第三種方法

公式: intersectionRatio > 0 && intersectionRatio <= 1

// 定義一個交叉觀察器
const io = new IntersectionObserver(ioes => {
    ioes.forEach(ioe => {
        const el = ioe.target
        const intersectionRatio = ioe.intersectionRatio
        if (intersectionRatio > 0 && intersectionRatio <= 1) {
            loadImg(el)
            io.unobserve(el)
        }
         el.onload = el.onerror = () => io.unobserve(el)
    })
})
// 執行交叉觀察器
function isInViewPortOfThree (el) {
    io.observe(el)
}
複製程式碼

5.4 相容性比較

在相容性方面,我們知道越原始的方法相容性是最好的,那麼第二種方法和第三種方法能否代替第三種方法呢?我們來看看。

getBoundingClientReact

IntersectionObserver

從caniuse的資料來看,getBoundingClientReact的適配情況很樂觀了。

所以,如果在移動端和桌面端都要做相容適配的話,方法二完全可以代替方法一進行適配了。如果僅僅是桌面端適配(比如運營後臺),我們或許可以嘗試下新的IntersectionObserver方法,畢竟IntersectionObserver裡面還有更多豐富的功能等著我們去體驗呢。

5.5 例項

有時,我們希望某些靜態資源(比如圖片),只有使用者向下滾動,它們進入視口時才載入,這樣可以節省頻寬,提高網頁效能。這就叫做"惰性載入",也稱為懶載入。

惰性載入預覽DEMO(放入你的本地圖片即可通過更換不同方法實現懶載入)

------------------------- 華麗的分割線 -----------------------------

關於我

一枚持證理財規劃師的程式設計師

微信公眾號二維碼

感謝以下資料提供的參考資訊

連結:戳這裡

內容:document.scrollingElement代替document.documentElement.scrollTop 和document.body.scrollTop

document.scrollingElement一統江湖 :

在桌面端document.scrollingElement就是document.documentElement;

在移動端document.scrollingElement就是document.body。

好處:避免了document.documentElement.scrollTop = 0(桌面端);document.body.scrollTop = 0(移動端);的相容性使用

相關文章