詳細解析 JavaScript 獲取元素的坐標


 引言

  最近突然看到了有關圖片懶加載的問題,大致意思就是初始狀態下頁面只加載瀏覽器可視區域的圖片,剩余圖片在當瀏覽器可視區域滾動到其位置時才開始加載。貌似現在許多大型網站都有實現懶加載,所以我便就此問題思考了一下。首先第一個問題是瀏覽器沒有相關的 API 方法可以檢測某個元素是否在可視區域,那么就只能我們人工計算,所以這里就涉及到了元素長寬,滾動條位置的知識。本文涉及的到的知識有元素長寬 clientWidth/offsetWidth/scrollWidth 的區別、以及 clientTop/offsetTop/scrollTop 的區別,並給了獲取元素坐標的源代碼。

 一、 clientWidth、offsetWidth、scrollWidth 的區別

  通常大家獲取元素的長寬的時候都會使用一些框架封裝好的方法,比如 jQuery.prototype.width() ,這些框架使用起來方便快捷,不過其中涉及到的知識還是非常多的,關於元素的長寬,有多種的獲取方法,其代表的實際意義也是不同的。

  簡單來說可以使用下列公式:

  clientWidth = width(可視區) + padding

  offsetWidth = width(可視區) + padding + border

  scrollWidth = width(內容區) 

  假設有我們以下一個元素:

1 #test {
2   width: 100px;
3   height: 100px;
4   margin: 10px;
5   border: 10px solid #293482;
6   padding: 10px;
7   background-color: yellow;
8   overflow: auto;
9 }
clientWidth offsetWidth scrollWidth
     

   以上 DEMO 是常規情況下的區別,下面加上一個滾動條我們們再來觀察以下:

clientWidth offsetWidth scrollWidth

注意這里不包括滾動條的長度

   

這里實際上相當於內容的寬度

 

 二、clientTop、offsetTop、scrollTop 的區別

  我們使用以下公式:

  clientTop = border

  offsetTop = 元素邊框外圍至父元素邊框內圍

  scrollTop = 元素可視區域頂部至實際內容區域的頂部

  給定以下兩個元素 container 和 test

 1 #container {
 2    background-color: #F08D8D;
 3    padding: 10px;
 4 }
 5 #test {
 6    position: relative;
 7    top: 10px;
 8    width: 100px;
 9    height: 100px;
10    margin: 20px;
11    border: 15px solid #293482;
12    padding: 10px;
13    background-color: yellow;
14 }
clientTop  offsetTop scrollTop

 
 

   

 三、獲取頁面元素絕對定位坐標

   有了以上知識基礎之后,我們現在需要考慮的問題是,如何獲取頁面元素的絕對位置,也就是在文檔流內容區的位置。我們知道,元素的 offsetTop 屬性可以獲取當前元素邊框外圍至父元素邊框內圍的的距離,clientTop 可以獲取元素邊框的寬度。那么現在用一個遞歸的公式就可以求得當前元素在頁面中的絕對位置:

  Element.absoluteTop = Element.parent.absoluteTop + Element.offsetTop + Element.clientTop;

  同理,我們用參照元素的長寬減去 left 和 top 和定位,即可得到 right 和 bottom 的定位;

   所以我們可以編寫以下工具來獲取元素的絕對位置,也就是在內容區的定位(參照元素必須是目標元素的祖先元素):

 1 var Position = {};
 2 (function () {
 3     Position.getAbsolute = function (reference, target) {
 4         //因為我們會將目標元素的邊框納入遞歸公式中,這里先減去對應的值
 5         var result = {
 6             left: -target.clientLeft,
 7             top: -target.clientTop
 8         }
 9         var node = target;
10         while(node != reference && node != document){
11             result.left = result.left + node.offsetLeft + node.clientLeft;
12             result.top = result.top + node.offsetTop + node.clientTop;
13             node = node.parentNode;
14         }
15         if(isNaN(reference.scrollLeft)){
16             result.right = document.documentElement.scrollWidth - result.left;
17             result.bottom = document.documentElement.scrollHeight - result.top;
18         }else {
19             result.right = reference.scrollWidth - result.left;
20             result.bottom = reference.scrollHeight - result.top;
21         }
22         return result;
23     }
24 })();

  此方法可以獲取一個元素相對於一個父元素的定位,如果要獲取元素在整張頁面,直接傳入 document 即可:

1 Position.getAbsolute(document, targetNode); //{left: left, right: right, top: top, bottom: bottom}

 四、獲取元素的可視區定位坐標

  在上一小節中,我們封裝了一個函數,這個函數可以用來獲取一個元素的相對於一個祖先元素的絕對定位坐標,在這一小節中,我們來獲取元素相對於瀏覽器窗口可視區域的定位坐標。在上一個函數中,我們可以獲取一個元素在 document 當中的定位,還記得我們在第二小節中的 scrollTop 屬性嗎?該屬性可以獲取滾動窗口可視區域頂端距離內容區頂端的距離,我們用元素的絕對定位坐標減去 document 的滾動定位就是我們想要的瀏覽器窗口定位啦(相對於瀏覽器左上角):

  ViewportTop = Element.absoluteTop - document.body.scrollTop;

  這里需要注意一個兼容性的問題,在 Chrome 中可以用 document.body.scrollTop 和 window.pageYOffset,IE 7/8 只能通過 document.documentElement.scrollTop 獲取, FireFox 和 IE9+ 可以用 document.documentElement.scrollTop 和 window.pageYOffset 獲取,Safari 需要 window.pageYOffset 獲取。所以這里我們需要做一下瀏覽器兼容:

  scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;

  注意這里的順序,在 IE7/8 中 window.pageYOffset 是 undefined ,document.body.scrollTop 在任何瀏覽器中都有,只是不支持的值為 0,如果表達式返回 undefined ,會影響后面的計算操作。而 || 運算符是一個短路取真運算符,所以我們要所有瀏覽器都有的 document.body.scrollTop 方法放在最后,關於 || 運算符的問題,可以參考 《探尋 JavaScript 邏輯運算符(與、或)的真諦》

  我們在剛才的工具上添加一個方法:

 1 var Position = {};
 2 (function () {
 3     Position.getAbsolute = function (reference, target) {
 4         var result = {
 5             left: -target.clientLeft,
 6             top: -target.clientTop
 7         }
 8         var node = target;
 9         while(node != reference && node != document){
10             result.left = result.left + node.offsetLeft + node.clientLeft;
11             result.top = result.top + node.offsetTop + node.clientTop;
12             node = node.parentNode;
13         }
14         if(isNaN(reference.scrollLeft)){
15             result.right = document.documentElement.scrollWidth - result.left;
16             result.bottom = document.documentElement.scrollHeight - result.top;
17         }else {
18             result.right = reference.scrollWidth - result.left;
19             result.bottom = reference.scrollHeight - result.top;
20         }
21         return result;
22     }
23     Position.getViewport = function (target) {
24         var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
25         var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
26         var absolutePosi = this.getAbsolute(document, target);
27         var Viewport = {
28             left: absolutePosi.left - scrollLeft,
29             top: absolutePosi.top - scrollTop,
30         }
31         return Viewport;
32     }
33 })();

  通過 Position.getViewport 方法可以獲取元素相對於瀏覽器窗口的定位:

1 Postion.getViewport(targetNode);  //{left: left, top: top}

 五、判斷可視區域

  在上面的幾個方法中,我們可以獲取元素的文檔流定位和視窗定位,不過這還是不能判斷一個元素是否在可視區域內,因為視窗定位可以是非常大的數字,這樣元素就在視窗的后面。這里我們需要使用瀏覽器視窗高度 window.innerHeight 屬性,在 IE8 以下需要用 document.documentElement.clientHeight 來獲取。

  windowHeight = window.innerHeight || document.documentElement.clientHeight;

  現在,我們用窗口的高度,減去相對於瀏覽器窗口的定位,即可獲取相對於瀏覽器窗口右下角的定位;

 1 var Position = {};
 2 (function () {
 3     Position.getAbsolute = function (reference, target) {
 4         var result = {
 5             left: -target.clientLeft,
 6             top: -target.clientTop
 7         }
 8         var node = target;
 9         while(node != reference && node != document){
10             result.left = result.left + node.offsetLeft + node.clientLeft;
11             result.top = result.top + node.offsetTop + node.clientTop;
12             node = node.parentNode;
13         }
14         if(isNaN(reference.scrollLeft)){
15             result.right = document.documentElement.scrollWidth - result.left;
16             result.bottom = document.documentElement.scrollHeight - result.top;
17         }else {
18             result.right = reference.scrollWidth - result.left;
19             result.bottom = reference.scrollHeight - result.top;
20         }
21         return result;
22     }
23     Position.getViewport = function (target) {
24         var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
25         var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
26         var windowHeight = window.innerHeight || document.documentElement.offsetHeight;
27         var windowWidth = window.innerWidth || document.documentElement.offsetWidth;
28         var absolutePosi = this.getAbsolute(document, target);
29         var Viewport = {
30             left: absolutePosi.left - scrollLeft,
31             top: absolutePosi.top - scrollTop,
32             right: windowWidth - (absolutePosi.left - scrollLeft),
33             bottom: windowHeight - (absolutePosi.top - scrollTop)
34         }
35         return Viewport;
36     }
37 })();

  現在我們使用 Position.getViewport(targetNode) 方法可以獲取元素左上角相對於窗口4個方向的定位:

1 Position.getViewport(targetNode);  //{left: left, top: top, right: right, bottom: bottom}

  有了這個方法,現在就可以真正的判斷元素是否在可視區域內了:

 1 var Position = {};
 2 (function () {
 3     Position.getAbsolute = function (reference, target) {
 4         //因為我們會將目標元素的邊框納入遞歸公式中,這里先減去對應的值
 5         var result = {
 6             left: -target.clientLeft,
 7             top: -target.clientTop
 8         }
 9         var node = target;
10         while(node != reference && node != document){
11             result.left = result.left + node.offsetLeft + node.clientLeft;
12             result.top = result.top + node.offsetTop + node.clientTop;
13             node = node.parentNode;
14         }
15         if(isNaN(reference.scrollLeft)){
16             result.right = document.documentElement.scrollWidth - result.left;
17             result.bottom = document.documentElement.scrollHeight - result.top;
18         }else {
19             result.right = reference.scrollWidth - result.left;
20             result.bottom = reference.scrollHeight - result.top;
21         }
22         return result;
23     }
24     Position.getViewport = function (target) {
25         var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
26         var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
27         var windowHeight = window.innerHeight || document.documentElement.offsetHeight;
28         var windowWidth = window.innerWidth || document.documentElement.offsetWidth;
29         var absolutePosi = this.getAbsolute(document, target);
30         var Viewport = {
31             left: absolutePosi.left - scrollLeft,
32             top: absolutePosi.top - scrollTop,
33             right: windowWidth - (absolutePosi.left - scrollLeft),
34             bottom: windowHeight - (absolutePosi.top - scrollTop)
35         }
36         return Viewport;
37     }
38     Position.isViewport = function (target) {
39         var position = this.getViewport(target);
40         //這里需要加上元素自身的寬高,因為定位點是元素的左上角
41         if(position.left + target.offsetWidth < 0 || position.top + target.offsetHeight < 0){
42             return false;
43         }
44         if(position.bottom < 0 || position.right < 0){
45             return false;
46         }
47         return true;
48     }
49 })();

  判斷理由很簡單,如果有一邊的定位是負值,那么元素就不在視窗內。

1 Position.getAbsolute(document, targetNode);  //獲取元素在文檔流中的絕對坐標
2 Position.getViewport(targetNode);  //獲取元素相對於瀏覽器視窗的坐標
3 Position.isViewport(targetNode);  //判斷元素是否在瀏覽器視窗內

 

   瀏覽器兼容性:

Chrome FireFox IE Safari Edge
Support Support IE8+ Support Support Support

  IE7 也可以使用,不過結果可能會有一點差異。

 擴展:圖片懶加載

  在文章的開始,我們提到過圖片懶加載的問題,那么具體需要怎么實現呢?這里只是給出一個思路:

  初始狀態下不設置 img 的 src,將圖片的真實 url 緩存在 Img 標簽上,我們可以設置為 data-src ,這樣圖片就不會加載了,隨后給鼠標添加 mousescroll 事情,每次鼠標滾動的時候將進入可視區域的圖片的 src 還原,這樣也就實現了圖片懶加載效果。不過初始狀態下需要將頁面可視區域的圖片先加載出來。

 參考文獻:

  阮一峰 — 用 JavaScript 獲取元素頁面元素位置

  張媛媛 — js實現一個圖片懶加載插件


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM