如何獲取頁面元素的位置
背景:最近在商品列表專案迭代中,需要在商品列表底部增加一個分銷商品廣告位,另外接收到一個產品曝光度的埋點需求,需要知道產品出現在使用者視口後在進行資料統計!
基於虛擬 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上查詢。
文件座標
任何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()
方法。它返回一個物件,其中包含了 left
、 top
、 width
、 height
等屬性。
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;
複製程式碼