如何獲取頁面元素的位置

背景:最近在商品列表項目迭代中,須要在商品列表底部增長一個分銷商品廣告位,另外接收到一個產品曝光度的埋點需求,須要知道產品出如今用戶視口後在進行數據統計!javascript

基於虛擬 DOM 數據驅動的思想,最不提倡的就是 jquery 時代的 DOM 操做!可是在目前一些複雜的頁面中常常仍是會用 javascript 處理一些 DOM 元素,實現一些動態效果;最多見的是用到一些元素的位置和尺寸的計算,可是其中瀏覽器的兼容性問題也是不可忽略的一部分,要想寫出預想效果的JavaScript代碼,咱們須要瞭解一些基本知識。java

基本概念

網頁大小:一張網頁的所有面積,就是它的大小。一般狀況下,網頁的大小由內容和 CSS 樣式表決定。 瀏覽窗口大小:指的是在瀏覽器窗口中看到的那部分網頁面積,又叫作 viewport (視口)。 若是網頁的內容可以在瀏覽器中所有顯示(也就不出現滾動條),那麼網頁的大小和瀏覽器窗口的大小是相等的。若是不能所有顯示,則滾動瀏覽器窗口,能夠顯示出網頁的各個部分。jquery

基本元素屬性

在每一個HTML元素都有下列屬性。瀏覽器

offsetWidth clientWidth scrollWidth
offsetHeight clientHeight scrollHeight
offsetLeft clientLeft scrollLeft
offsetTop clientTop scrollTop

爲了理解方便這些屬性,咱們須要知道 HTML 元素的實際內容有可能比分配用來容納內容的盒子更大,所以可能會出現滾動條,內容區域是視口,當實際內容比視口大的時候,須要把元素的滾動條位置考慮進去。函數

  • clientHeight 和 clientWidth 用於描述元素內尺寸,是指元素內容+內邊距大小,不包括邊框(IE下實際包括)、外邊距、滾動條部分
  • offsetHeight 和 offsetWidth 用於描述元素外尺寸,是指元素內容+內邊距+邊框,不包括外邊距和滾動條部分
  • clinetTop 和 clinetLeft 返回內邊距的邊緣和邊框的外邊緣之間的水平和垂直距離,也就是左,上邊框寬度
  • offsetTop 和 offsetLeft 表示該元素的左上角(邊緣外邊框)與已定位的父容器(offsetParent對象)左上角的距離
  • offsetParent 對象是指元素最近的定位(relative、absolute)祖先元素,遞歸上溯,若是沒有祖先元素是定位的話,會返回 null

獲取視口大小

網頁上的每一個元素,都有 clientHeight 和 clientWidth屬性。這兩個屬性指元素的內容部分再加上 padding 的所佔據的視覺面積,不包括 border 和滾動條佔用空間。工具

所以,document 元素的 clientHeight 和 clientWidth 屬性,就表明了網頁的大小ui

function getViewport() {
  if(!document) {
    return {}
  }
  if (document.compatMode === 'BackCompat') {
    return {
      width: document.body.clientWidth,
      height: document.body.clientHeight
    };
  }
  return {
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  };
}
複製代碼

上面的 getViewport 函數就是能夠返回瀏覽器窗口的高和寬。使用的時候,有三個地方須要注意:spa

  • 該函數必須在頁面加載完成後才能運行,不然 document 對象尚未生成,瀏覽器會報錯。
  • 大多數狀況下,都是 document.documentElement.clientWidth 返回正確值。可是,在 IE6 的 quirks 模式中, document.body.clientWidth 返回正確的值,所以函數中加入了對文檔模式的判斷
  • clientWidth 和 clientHeight 都是隻讀屬性,不能對它們賦值。

獲取視口大小的另外一種方式

網頁中的每個元素還有 srcollHeight 和 scrollWidth 屬性,指包含滾動在內的該元素的視覺面積。 那麼,document 對象的 scrollHeight 和 scrollWidth 屬性就是網頁的大小,意思就是滾動條滾過的全部長度和寬度。code

仿照 getViewport 函數,能夠寫出 getPagearea() 函數。cdn

function getPagearea() {
 if (document.compatMode == 'BackCompat') {
  return {
     width: Math.max(document.body.scrollWidth, document.body.clientWidth),
     height: Math.max(document.body.scrollHeight, document.body.clientHeight)
    };
 }
 return {
    width: Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth),
   height: Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight)
  };
}

複製代碼

相對文檔與視口的座標

當咱們在計算一個 DOM 元素位置也就是座標的時候,會涉及到兩種座標系, 文檔座標__和__視口座標。 咱們常常用到的document就是整個頁面部分,而不只僅是窗口可見部分,還包括由於窗口大小限制而出現滾動條的部分,它的左上角就是咱們所謂相對於文檔座標的原點。

視口是顯示文檔內容的瀏覽器的一部分,它不包括瀏覽器外殼(菜單,工具欄,狀態欄等),也就是當前窗口顯示頁面部分,不包括滾動條。

若是文檔比視口小,說明沒有出現滾動,文檔左上角和視口左上角相同,通常來說在兩種座標系之間進行切換,須要加上或減去滾動的偏移量(scroll offset)。

爲了在座標系之間進行轉換,咱們須要斷定瀏覽器窗口的滾動條位置。window對象的pageXoffset和pageYoffset提供這些值,IE 8及更早版本除外。也能夠經過scrollLeft和scrollTop屬性得到滾動條位置,正常狀況下經過查詢文檔根節點(document.documentElement)來得到這些屬性值,但在怪異模式下必須經過文檔的body上查詢

image.png | left | 500x374

文檔座標

任何HTML元素都擁有offectLeft和offectTop屬性返回元素的X和Y座標,對於不少元素,這些值是文檔座標,可是對於以定位元素後代及一些其餘元素(表格單元),返回相對於祖先的座標。咱們能夠經過簡單的遞歸上溯累加計算

function getElementPosition(e) {
  let x = 0;
  let y = 0;
  while (e != null) {
    x += e.offsetLeft;
    y += e.offsetTop;
    e = e.offsetParent;
  }
  return { x, y };
}
複製代碼

儘管如此,這個函數也不老是計算正確的值,當文檔中含有滾動條的時候這個方法就不能正常工做了,咱們只能在沒有滾動條的狀況下使用這個方法,不過咱們用這個原理算出一些元素相對於某個父元素的座標。

快速方法:

網頁元素的相對位置就是:

let X = element.getBoundingClientRect().left;
let Y = element.getBoundingClientRect().top;
複製代碼

視口座標

計算視口座標就相對簡單了不少,能夠經過調用元素getBoundingClientRect  方法。方法返回一個有left、right、top、bottom屬性的對象,分別表示元素四個位置的相對於視口的座標。getBoundingClientRect 所返回的座標包含元素的內邊距和邊框,不包含外邊距。兼容性很好,很是好用

function getElementViewTop(element) {
  let actualTop = element.offsetTop;
  let current = element.offsetParent;
  let elementScrollTop;

  while (current !== null) {
    actualTop += current.offsetTop;
    current = current.offsetParent;
  }

  if (document.compatMode == 'BackCompat') {
    elementScrollTop = document.body.scrollTop;
  } else {
    elementScrollTop = document.documentElement.scrollTop;
  }

  return actualTop - elementScrollTop;
}


function getElementViewLeft(element) {
  let actualLeft = element.offsetLeft;
  let current = element.offsetParent;
  let elementScrollLeft;

  while (current !== null) {
    actualLeft += current.offsetLeft;
    current = current.offsetParent;
  }

  if (document.compatMode == 'BackCompat') {
    elementScrollLeft = document.body.scrollLeft;
  } else {
    elementScrollLeft = document.documentElement.scrollLeft;
  }

  return actualLeft - elementScrollLeft;
}
複製代碼

快速方法:

使用 getboundingClientRect() 方法。它返回一個對象,其中包含了 lefttopwidthheight 等屬性。

let X = element.getBoundingClientRect().left;
let Y = element.getBoundingClientRect().top;
複製代碼

再加上滾動 距離,就能夠獲得絕對位置:

const X= element.getBoundingClientRect().left+document.documentElement.scrollLeft;
const Y =element.getBoundingClientRect().top+document.documentElement.scrollTop;
複製代碼
相關文章
相關標籤/搜索