判斷元素是否在視窗以內

做爲一名前端工程師咱們常常須要判斷目標元素是否在視窗以內或者和視窗的距離小於一個值(例如 100 px),從而實現一些經常使用的功能,例如:前端

  • 圖片的懶加載
  • 列表的無限滾動
  • 計算廣告元素的曝光狀況
  • 可點擊連接的預加載

說明:可點擊連接的預加載 這個功能目前使用的網站還比較少,其實就是「預先獲取頁面可視區域內的連接,加快後續加載速度」,能極大提高用戶在站內跳轉時的體驗,由 Google 在 2018 年年末經過 quicklink 項目 進行開源。git

目前流行的方式是經過 Element.getBoundingClientRect() 拿到元素的相關位置信息後進行手動的判斷,可是這種方法因爲運行在 JavaScript 的主進程上,因此當須要監聽的元素較多時,可能會形成性能問題。github

那麼仔細想想,其實在瀏覽器渲染的時候,它就知道了元素是否在視窗以內,自身面積有多少在視窗以內。因此最高效的方式就是預先告訴瀏覽器當目標元素和視窗重疊的時候,咱們要搞事情,而後等着瀏覽器執行回調函數便可。出於這種考慮,W3C 提出了 Intersection Observer API瀏覽器

我作了一個小實驗,建立了一個十萬個節點的長列表,當節點滾入到視窗中時,背景就會從紅色變爲黃色。前端工程師

下圖是使用 Element.getBoundingClientRect() 進行計算實現的效果,能夠看到有很是明顯的卡頓,主要是由於須要對每個元素都進行計算,判斷它們是否在視窗以內。具體的代碼能夠點擊查看 Code Pen函數

Element.getBoundingClientRect() 實現

下圖是使用 Intersection Observer API 進行註冊回調實現的效果,能夠看出來十分流暢。具體的代碼能夠點擊查看 Code Pen性能

Intersection Observer API

本文接下來就分別介紹這兩種方法。網站

Element.getBoundingClientRect() - 手動計算

經過 Element.getBoundingClientRect(),咱們能夠拿到元素在視窗內的位置,包括其距離視窗的上下左右的距離和它自身的寬高。ui

const target = document.querySelector('.target');
const clientRect = target.getBoundingClientRect();

// log data
console.log(clientRect);

// {
// bottom: 556.21875,
// height: 393.59375,
// left: 333,
// right: 1017,
// top: 162.625,
// width: 684
// }
複製代碼

能夠經過來自 MDN 的一張圖進行說明:spa

MDN - getBoundingClientRect()

若是一個元素在視窗以內的話,那麼它必定知足下面四個條件:

  • top 大於等於 0
  • left 大於登陸 0
  • bottom 小於等於視窗高度
  • right 小於等於視窗寬度

考慮到不一樣瀏覽器的兼容性,能夠寫出來以下的函數用於判斷元素是否在視窗以內:

function isInViewPort(element) {
  const viewWidth = window.innerWidth || document.documentElement.clientWidth;
  const viewHeight = window.innerHeight || document.documentElement.clientHeight;
  const {
    top,
    right,
    bottom,
    left,
  } = element.getBoundingClientRect();

  return (
    top >= 0 &&
    left >= 0 &&
    right <= viewWidth &&
    bottom <= viewHeight
  );
}

// usage
console.log(isInViewPort(document.querySelector('.target'))); // true or false
複製代碼

Intersection Observer API 註冊回調

Intersection Observer 即重疊觀察者,從這個命名就能夠看出它用於判斷兩個元素是否重疊。

這個 API 使用十分簡單,只需兩步:建立觀察者傳入被觀察者

建立觀察者

const options = {
  // 表示重疊面積佔被觀察者的比例,從 0 - 1 取值,
  // 1 表示徹底被包含
  threshold: 1.0, 
};

const callback = (entries, observer) => { ....}

const observer = new IntersectionObserver(callback, options);
複製代碼

經過上面的幾行代碼就建立了觀察者 observer,傳入的參數 callback 在重疊比例超過 threshold 時會被執行。

說明:options 支持傳入更多的參數來指定根元素,未傳入時使用視窗元素。

傳入被觀察者

const target = document.querySelector('.target');
observer.observe(target);

// 上段代碼中被省略的 callback
const callback = function(entries, observer) { 
    entries.forEach(entry => {
        entry.time;               // 觸發的時間
        entry.rootBounds;         // 根元素的位置矩形,這種狀況下爲視窗位置
        entry.boundingClientRect; // 被觀察者的位置舉行
        entry.intersectionRect;   // 重疊區域的位置矩形
        entry.intersectionRatio;  // 重疊區域佔被觀察者面積的比例(被觀察者不是矩形時也按照矩形計算)
        entry.target;             // 被觀察者
    });
};
複製代碼

經過 observer.observe(target) 這一行代碼便可簡單的註冊被觀察者

注意:目前在瀏覽器的原生支持還不是很好,可使用 w3c - IntersectionObserver Polifill 進行兼容。

相關文章
相關標籤/搜索