壹 ❀ 引
我在 JQ的offset().top與js的offsetTop區別詳解 這篇博客中詳細分析了JQ方法offset().top與JS屬性offsetTop的區別,並得出了一條offset().top = offsetTop - scrollTop的結論,不過此結論只適用於監聽元素滾動條,而window的滾動條並不滿足。那么在滾動window滾動條時如何獲取元素距離視窗頂部的距離呢,這就不得說說本文的主角getBoundingClientRect方法。
貳 ❁ 關於getBoundingClientRect()
我們可以先拷貝下面的代碼,動手起來跟着操作一遍,印象會深刻,需要引入JQ,這里提供一個靜態資源地址,進去搜索JQ直接復制地址引入即可:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="css/demo.css"> </head> <body> <div class="child"></div> </body> <script src="https://cdn.staticfile.org/jquery/3.4.1/jquery.js"></script> <script src="js/app.js"></script> </html>
* { padding: 0; margin: 0; list-style: none; outline: none; } .child { margin-top: 200px; margin-left: 200px; height: 200px; width: 200px; background: #bbded6; border: 5px solid #8ac6d1; }
// JS getBoundingClientRect() let child = document.querySelector('.child'); console.log(child.getBoundingClientRect()); //JQ offset() // let son = $('.child'); // son.offset(); // JS offsetTop // child.offsetTop;
這里我們在頁面定義了一個寬高各200px且包含5px邊框的盒子,由於沒設置box-size = ’border-box'屬性,所以盒子總寬高為210px,這里我們直接打印出getBoundingClientRect方法結果,如下:

可以看到getBoundingClientRect()返回了一個 DOMRect 對象,該對象包含了元素多個屬性,其中最顯眼的width與height毫無懸念就是元素寬高,除此之外還包含top,bottom,right,left與x,y屬性。
不難猜出,top和left為元素左上頂點距離視窗頂端與左側距離,而right與bottom為右下頂點距離視窗左側與頂端距離。而x與left一致,y與top一致。

現在修改代碼,將css中的margin-top修改為1000px,同時使用scroll事件打印getBoundingClientRect的top值與滾動條距離,其它不變,像這樣:
window.onscroll = function () { //輸出top值與滾動條距離 console.log(child.getBoundingClientRect().top, document.documentElement.scrollTop); };
當滾動條滾動,可以發現getBoundingClientRect().top值加上document.documentElement.scrollTop的值終等於1000,而這1000正是元素距離視窗頂部的距離。

現在將JQ的代碼與輸出offsetTop的代碼取消注釋,並加入到scroll事件中,再次滾動滾動條像這樣:
// JS getBoundingClientRect() let child = document.querySelector('.child'); let son = $('.child'); window.onscroll = function () { //輸出getBoundingClientRect的top值,JQ的offset().top與offsetTop console.log(child.getBoundingClientRect().top,son.offset().top,child.offsetTop); };

可以看到getBoundingClientRect().top是順着滾動條下移慢慢變小的,畢竟元素距離視窗頂部越來越近了,而offset().top與offsetTop一直保持1000,為什么呢?
首先,我們知道offsetTop獲取的是元素距離自己最近的offsetParent(position為非static且距離自己最近的祖先元素)的距離,且這個距離不隨滾動條滾動變化,也就是說這個距離開始是多少就是多少,是個恆定值。
而JQ的offset().top獲取的是元素距離文檔頂端的距離。怎么去理解這個文檔呢,我們把瀏覽器網頁可讀范圍比喻成一幅畫,這幅畫包含了html,所以如果html內容越多(比如給html設置上margin),這幅畫就會越長;而視窗呢可以比喻成一塊玻璃,我們透過玻璃看這幅畫,如果畫的高度比玻璃還高那就有了滾動條,當滾動往下拉時,等同於玻璃位置不變,但是畫會往上移。
知道這個概念后,我們再來想想上面例子為什么offset().top一直不變,當滾動條下拉,畫往上移,而畫中的元素是隨着畫一起運動的,很明顯該元素相對畫頂端的距離也一直沒變,這下總知道為啥是1000了吧。
而getBoundingClientRect獲取的是元素距離視窗頂端的距離,當畫上移,元素肯定距離視窗頂端越來越近,所以這個距離一定越來越小。
叄 ✿ getBoundingClientRect與offset().top的區別
那么到這,我們知道了getBoundingClientRect參照是視窗頂端,而JQ的offset().top參照的是文檔,兩者參照對象不同。
當監聽的是window的滾動條時,元素的getBoundingClientRect().top會原來越小,而offset().top一直不變。
當監聽某個元素的滾動條時,元素的getBoundingClientRect().top會與offset().top保持一致,比如我讓一個ul包含了多個li,滾動ul的滾動條時,獲取第一個li的相關屬性,有興趣可以將下面的代碼替換一下:
HTML <ul> <li style="background: orange;">1</li> <li style="background: black;">2</li> <li style="background: blueviolet;">3</li> <li style="background: pink;">4</li> </ul> CSS * { padding: 0; margin: 0; list-style: none; outline: none; box-sizing: border-box; } ul { width: 200px; height: 100px; overflow-y: scroll; margin: 100px; } li { width: 100px; height: 300px; line-height: 300px; text-align: center; } JS let parent = document.querySelector('ul'); let child = document.querySelectorAll('li')[0]; parent.onscroll = function () { //輸出getBoundingClientRect的top值,JQ的offset().top與offsetTop console.log(child.getBoundingClientRect().top, $(child).offset().top, child.offsetTop); };

可以看到getBoundingClientRect().top與offset().top的值完全一致。
肆 ❤ 獲取元素距離距離視窗頂部的可變距離
樓層導航,懶加載,返回頂部按鈕等等,只要涉及scroll事件,都無法避免的要去計算某個元素距離視窗頂部的距離,經過前文的分析,不管是監聽window滾動條還是元素滾動條,其實都有兩種可行方法。
如果是監聽的是window的滾動條,那么可以使用:
window.onscroll = function () { 可變距離 = document.querySelector('元素').getBoundingClientRect().top };
其次可以獲取元素的offsrtTop減去滾動條距離,前提是得保證元素的offsetParent為html元素或者body:
var offsetTop = document.querySelector('元素').offsetTop; window.onscroll = function () { 可變距離 = offsetTop - document.documentElement.scrollTop; };
如果監聽的是元素的滾動條,獲取元素內子元素距離視窗的高度可以使用JQ的offset().top:
document.querySelector('父元素').onscroll = function () {
子元素可變距離 = $('子元素元素').offset().top;
};
其次可以使用子元素的offsetTop減去父元素滾動條距離,當然你也得保證子元素的offsetParent為html或body:
var parent = document.querySelector('父元素'); var son = document.querySelector('子元素'); parent.onscroll = function () { 子元素可變距離 = son.offsetTop - parent.scrollTop; };
伍 ☸ 總
那么結合文章開頭的另一篇博客以及本文,我們詳細介紹了JQ的offset().top與JS的offsetTop以及getBoundingClientRect的區別:
我們知道了offset().top參照對象是文檔上邊界,當監聽window滾動條,它的效果與offsetTop類似,都是固定不變的值,畢竟在元素上移時,整體的文檔也上移了。
我們知道offsetTop的參照對象是一個可變的offsetParent,而且得到的值永遠不變。
我們還知道了好用的getBoundingClientRect方法,它的參照對象是視窗頂端,不管滾動條是window還是元素的,我們可以時時拿到可變尺寸。
當然我們還了解了特定情況下,使用offsetTop - scrollTop一樣能拿到這個可變值。
那么到這里,本文結束。
