1. 程式人生 > >如何獲取頁面元素的位置

如何獲取頁面元素的位置

背景:最近在商品列表專案迭代中,需要在商品列表底部增加一個分銷商品廣告位,另外接收到一個產品曝光度的埋點需求,需要知道產品出現在使用者視口後在進行資料統計!

基於虛擬 DOM 資料驅動的思想,最不提倡的就是 jquery 時代的 DOM 操作!但是在目前一些複雜的頁面中經常還是會用 javascript 處理一些 DOM 元素,實現一些動態效果;最常見的是用到一些元素的位置和尺寸的計算,但是其中瀏覽器的相容性問題也是不可忽略的一部分,要想寫出預想效果的JavaScript程式碼,我們需要了解一些基本知識。

基本概念

網頁大小:一張網頁的全部面積,就是它的大小。通常情況下,網頁的大小由內容和 CSS 樣式表決定。 瀏覽視窗大小:指的是在瀏覽器視窗中看到的那部分網頁面積,又叫做 viewport

(視口)。 如果網頁的內容能夠在瀏覽器中全部顯示(也就不出現滾動條),那麼網頁的大小和瀏覽器視窗的大小是相等的。如果不能全部顯示,則滾動瀏覽器視窗,可以顯示出網頁的各個部分。

基本元素屬性

在每個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 屬性,就代表了網頁的大小

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 函式就是可以返回瀏覽器視窗的高和寬。使用的時候,有三個地方需要注意:

  • 該函式必須在頁面載入完成後才能執行,否則 document 物件還沒有生成,瀏覽器會報錯。
  • 大多數情況下,都是 document.documentElement.clientWidth 返回正確值。但是,在 IE6 的 quirks 模式中, document.body.clientWidth 返回正確的值,因此函式中加入了對文件模式的判斷
  • clientWidth 和 clientHeight 都是隻讀屬性,不能對它們賦值。

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

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

仿照 getViewport 函式,可以寫出 getPagearea() 函式。

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;
複製程式碼