做爲一名前端工程師咱們常常須要判斷目標元素是否在視窗以內或者和視窗的距離小於一個值(例如 100 px),從而實現一些經常使用的功能,例如:前端
說明:可點擊連接的預加載 這個功能目前使用的網站還比較少,其實就是「預先獲取頁面可視區域內的連接,加快後續加載速度」,能極大提高用戶在站內跳轉時的體驗,由 Google 在 2018 年年末經過 quicklink 項目 進行開源。git
目前流行的方式是經過 Element.getBoundingClientRect()
拿到元素的相關位置信息後進行手動的判斷,可是這種方法因爲運行在 JavaScript 的主進程上,因此當須要監聽的元素較多時,可能會形成性能問題。github
那麼仔細想想,其實在瀏覽器渲染的時候,它就知道了元素是否在視窗以內,自身面積有多少在視窗以內。因此最高效的方式就是預先告訴瀏覽器當目標元素和視窗重疊的時候,咱們要搞事情,而後等着瀏覽器執行回調函數便可。出於這種考慮,W3C 提出了 Intersection Observer API
。瀏覽器
我作了一個小實驗,建立了一個十萬個節點的長列表,當節點滾入到視窗中時,背景就會從紅色變爲黃色。前端工程師
下圖是使用 Element.getBoundingClientRect()
進行計算實現的效果,能夠看到有很是明顯的卡頓,主要是由於須要對每個元素都進行計算,判斷它們是否在視窗以內。具體的代碼能夠點擊查看 Code Pen。函數
下圖是使用 Intersection Observer API
進行註冊回調實現的效果,能夠看出來十分流暢。具體的代碼能夠點擊查看 Code Pen。性能
本文接下來就分別介紹這兩種方法。網站
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
若是一個元素在視窗以內的話,那麼它必定知足下面四個條件:
考慮到不一樣瀏覽器的兼容性,能夠寫出來以下的函數用於判斷元素是否在視窗以內:
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 進行兼容。