對於每一位Web開發的同學而言,在CSS的世界當中,每一個元素都是一個盒子,都有描述盒子大小和位置的相關屬性。比如CSS的盒模型相關的屬性和position
相關屬性。不過今天我們學習和聊的不是CSS的世界,而是來學習和聊JavaScript中怎麼獲取元素尺寸和位置。
在JavaScript中也有很多屬性允許我們讀取有關元素的width
、height
和其他幾何特性的信息。對於元素的位置獲取和控制,在JavaScript中與CSS有所不同,移動或定位元素時,經常需要它們來正確地計算坐標。
在這篇教程中,將學習JavaScript如何獲取HTML元素的確切位置和大小,並瞭解它們的工作原理。
在大多數情況下,元素的位置取決於其自身的CSS屬性,但在很大程度上取決於其父元素的CSS屬性。這裡所指的屬性主要是padding
、margin
和border
。
比如下面這個簡單示例,示例中名為example
的div
元素的盒模型視圖可以很好地顯示這些屬性如何影響佈局:
#example { width: 300px; height: 200px; border: 25px solid #E8C48F; padding: 20px; overflow: auto;}
盒模型的視覺圖如下所示:
通過瀏覽器開發者工具可以很輕易的看到元素#example
的盒模型視圖。視圖中清晰的表示了padding
、margin
和border
的值是如何表示的。而且在視圖中,可以看到每個CSS屬性,以及其對應的值。
#example
元素它有border
、padding
和滾動條(因為我們顯式設置了元素的width
和height
,並且將overflow
設置為auto
)。元素沒有margin
,因為不是元素本身的一部分,也沒有為它提供特殊屬性。
如果將#example
元素上的各個屬性繪製到圖形,看起來像下面這樣:
上面的圖片展示了元素有滾動條的情形,這也是最複雜的情況。有些瀏覽器(不是所有)通過從內容中獲取空間來為它保留空間。
因此,如果沒有滾動條,內容(content
)的width
將是300px
,但是如果滾動條的寬度是16px
(不同的設備和瀏覽器的寬度不同,如下圖所示),那麼內容的width
將是300 - 16 = 284px
。在具體使用的過程中,我們應該要考慮到這一點。這也就是為什麼要舉一個帶有滾動條的示例。如果沒有滾動條,那麼事情就會變得簡單點。
有關於瀏覽器滾動條的特性,可以點擊這裡進行瞭解。
上面的示例,由於我們的元素屬於正常的文檔流,從瀏覽器開發者工具中截出的元素盒模型視圖,只看到了元素尺寸相關的屬性,但並沒有看到元素有關於位置的尺寸。如果我們在元素中添加position
相關的屬性,那麼就可以看到。比如下面這個示例:
#container { padding: 24px; margin: 24px; border: 50px #ccc solid; left: 10px; top: 200px; position: absolute;}
元素#container
的盒模型視圖如下:
元素提供的width
、height
和其他幾何結構的屬性,其值總是數字。它們被假定為以像素為單位。總體情況如下圖所示:
其實它有很多屬性,我們很難把它們都放在一張圖中,我在網上找了一張描述相對全一點的圖:
雖然這圖上的屬性,在CSS的世界中並沒有看過,但它們的值很簡單,也很容易理解。JavaScript就是通過這些屬性來獲取元素的位置或元素的尺寸。
讓我們從元素的外部開始探索它們。
offsetParent
、offsetLeft
和offsetTop
三個屬性是“最外層”的幾何結構的屬性,因此我們從這幾個屬性著手開始學習。
**offsetParent**
:返回一個指向最近的(closest
,指包含層級上的最近)包含該元素的定位元素。如果沒有定位的元素,則offsetParent
為最近的table
元素對象或根元素(標準模式下為<html>
元素,怪異模式下為<body>
元素)。當元素的style.display
設置為none
或position
為fixed
時,offsetParent
返回null
。
在大多數實際情況下,可以使用offsetParent
獲取最近的CSS位置(CSS-Positioned)的祖先。而其中offsetleft
和offsetTop
提供相對於左上角的x
和y
坐標。
比如下面這個示例,內部的<div>
元素具有<main>
作為offsetParent
,而offsetLeft
和offsetTop
從左上角移動180px
(即向右下角方向移動)。
<main style="position: relative" id="main"> <article> <div id="example" style="position: absolute; left: 180px; top: 180px">...</div> </article></main>let exampleEl = document.getElementById('example')console.log(exampleEl.offsetParent)
從console.log()
輸出的結果可以看到,元素#example
的offsetParent
是main
元素。通過offsetLeft
和offsetTop
輸出的值為180
。
特別注意,有幾種情況之下,offsetParent
返回的值為null
:
display:none
或元素就不在document
中)<body>
和<html>
元素position:fixed
的元素從上圖中大家對offsetLeft
和offsetTop
或許有所感知,但還是花一點篇幅來描述:
**offsetLeft**
:當前元素左上角相對於offsetParent
節點的左邊界偏移的像素值。對塊級元素來說,offsetTop
、offsetLeft
、offsetWidth
及 offsetHeight
描述了元素相對於 offsetParent
的邊界框。然而,對於可被截斷到下一行的行內元素(如 span
),offsetTop
和 offsetLeft
描述的是第一個邊界框的位置(使用 Element.getClientRects()
來獲取其寬度和高度),而 offsetWidth
和 offsetHeight
描述的是邊界框的尺寸(使用 Element.getBoundingClientRect
來獲取其位置)。因此,使用 offsetLeft
、offsetTop
、offsetWidth
、offsetHeight
來對應 left
、top
、width
和 height
的一個盒子將不會是文本容器 span
的盒子邊界。**offsetTop**
:當前元素相對於其 offsetParent
元素的頂部的距離。事實上,每個元素的父元素都有一個相似的偏移量值,所以我們的循環只是將它們加起來,直到沒有父元素為止。最終的結果是所有偏移量的總和,以幫助我們為元素獲取一個精確的位置。該位置考慮了margin
、padding
和top
、left
相關的值。但有一件事情是遺漏了的。
偏移量屬性沒有考慮的一個值是元素的border
。原因是,border
被認為是內部元素的左上角的一部分,但是它的大小對某些東西的位置有影響。為了測量border
的大小,我們使用clientLeft
和clientTop
來獲取。這兩個屬性我們後面會介紹。
上面我們看了元素外部影響元素的相關屬性。現在我們來看看元素自身相關的屬性。主要有offsetWidth
和offsetHeight
:
offsetWidth
:一個元素的佈局寬度。offsetWidth
是測量包含元素的邊框、水平線上的內邊距、豎直方向滾動條以及CSS設置的寬度的值。offsetHeight
:元素的像素高度,高度包含該元素的垂直內邊距和邊框,且是一個整數。通常,元素的offsetHeight
是一種元素CSS高度的衡量標準,包括元素的邊框、內邊距和元素的水平滾動條(如果存在且渲染的話),不包含:before
或:after
等偽類元素的高度。對於文檔的body對象,它包括代替元素的CSS高度線性總含量高。浮動元素的向下延伸內容高度是被忽略的。offsetWidth
和offsetHeight
提供元素的“外部”的寬度和高度。換句話說,包括border
在內的全部大小。
為了更易於理解,還是用張圖來闡述:
在實際使用之中,不顯示的元素其幾何結構屬性的值為0
或null
。也就是說,幾何結構屬性只對可見的元素進行計算。
如果一個元素(或它的任何一個祖先元素)的display:none
或不在文檔中,那麼所有的幾何屬性都為0
或null
。那麼問題來了,我們應該怎麼去判斷呢?為瞭解決這個問題我們可以寫一個函數。比如:
function isHidden(elem) { return !elem.offsetWidth && !elem.offsetHeight;}
請注意,對於屏幕上的元素,
isHidden
返回的值是true
,但是沒有任何大小,比如一個空的<div>
。
前面提到過offsetLeft
和offsetTop
沒有考慮border
。為了測量border
的大小,我們使用clientLeft
和clientTop
來獲取。接下來我們來瞭解和學習clientTop
和clientLeft
。
比如上面的示例:
border-left
的寬度:clientLeft = 25
border-top
的寬度:clientTop = 25
但準確地說,它們不是border
,而是內部與外部的相對坐標。那麼有什麼區別呢?
比如這樣的一個示例,當文檔是從右到左排版(操作系統是阿拉伯語或希伯來語)時,也就是direction
為rtl
,它就可見了。然後滾動條不在右邊,而是在左邊,這個時候clientLeft
的值也包含了滾動條的寬度。
基於上面的示例,如果direction:rtl
,這個時候clientLeft
的值就變成了25 + 16 = 41
。
clientWidth
和clientHeight
屬性可以用來獲取元素邊框內區域的大小。它們包括了內容的寬度和padding
,但不包含滾動條寬度:
先來看上圖是有關於clientHeight
的值,因為它更容易。主要是沒有水平滾動條,所以它正好是邊框內區域的總和:height + padding-top + padding-bottom
,即:200 + 2 * 20 = 240px
。
再來看clientWidth
。這裡的內容寬度不是300px
,而是284px
,因為還有一個16px
寬的滾動條。所以clientWidth
的值是284 + 20 * 2 = 324px
。
如果沒有padding
,那麼clientWidth
和clientHeight
就是border
和滾動內的內容區域寬度和高度。
也就是說,當沒有padding
時,clientWidth
和clientHeight
可以用來獲取內容區域的width
和height
。
現在,使用offset*
和client*
屬性,我們已經考慮了padding
、margin
、border
、top
和left
相關的屬性。在99%的情況之下,這應該是我們需要考慮的全部,除非你的運用場景是剩下的那1%部分。
很多時候,元素有可能是位於帶有滾動條的容器之中。如果是這樣的話,為了確保能準確的獲取到元素的位置,還需要通過scrollLeft
和scrollTop
屬性來進行測量,並從總數中減去它們。
scrollLeft
和scrollTop
屬性可以獲取或設置滾動元素隱藏部分的寬度和高度。
scrollLeft
可以讀取或設置元素滾動條到元素左邊的距離。如果這個元素的內容排列方向(direction
) 是rtl
(right-to-left
) ,那麼滾動條會位於最右側(內容開始處),並且scrollLeft
值為0
。此時,當你從右到左拖動滾動條時,scrollLeft
會從0
變為負數。
scrollTop
可以獲取或設置一個元素的內容垂直滾動的像素數。 一個元素的 scrollTop
值是這個元素的頂部到它的最頂部可見內容(的頂部)的距離的度量。當一個元素的內容沒有產生垂直方向的滾動條,那麼它的 scrollTop
值為0
。
在下面的圖片中,我們可以看到在一個塊中垂直滾動條的scrollHeight
和scrollTop
:
大多數幾何結構屬性都是只讀的,但是scrollLeft
和scrollTop
是可更改的。如果將scrollTop
設置為0
或Infinity
將會使元素分別滾動到瀏覽器的最頂端和最底端。
clientWidth
和clientHeight
僅負責元素的可見部分。而屬性scrollWidth
和scrollHeight
還會包括不可見(隱藏)的部分。
scrollWidth
返回該元素區域寬度和自身寬度中較大的一個,若自身寬度大於內容寬度(存在滾動條),那麼scrollWidth
將大於clientWidth
。需要注意的是,改屬性返回的是四捨五入後的整數值,如果需要小數,則需要使用getBoundingClientRect()
。
scrollHeight
返回該元素內容高度。包括被overflow
隱藏掉的部分,包含padding
,但不包含margin
。和scrollWidth
類似,如果需要小數,則需要使用getBoundingClientRect()
。
比如:
上圖中可以看出:
scrollHeight = 723
是指元素整個內容的高度,還包括隱藏的部分;scrollWidth = 324
是指元素整個內容的寬度,這裡沒有水平滾動條,所以它等於clientWidth
。
我們可以使用這些屬性將元素擴展到它的寬度和高度。
element.style.height = `${element.scrollHeight}px`;
除些之外,這兩個屬性最常見的使用場景就是:判斷元素是否滾動到底部,比如下面的代碼,如果返回的值為true
,表示滾動到底部,反之則不是:
ele.scrollHeight - ele.scrollTop === ele.clientHeight
前面介紹的一些屬性都是有關於DOM元素的幾何結構屬性。在JavaScript中通常用來計算寬度、高度和距離。但在“樣式和類”一節中學習中我們知道,在JavaScript中可以使用getComputedStyle
來讀取CSS中的width
和height
。
既然如此,那麼為什麼不這樣來讀取元素的寬度呢?
let elem = document.body;console.log( getComputedStyle(elem).width );
其實我們使用幾何結構屬性,其實是有原因所在的:
首先,CSS的width
和height
取決於另一個屬性:box-sizing
。在CSS中,這個屬性定義了元素的寬度和高度。換句話說,CSS可以改變元素盒模型的大小,而這種改變有可能會破壞JavaScript的幾何結構屬性取得的值。
其次,CSS的寬度和高度可以是auto
,例如一個內聯元素span
。
還有一個原因,就是滾動條。因為滾動條會佔用一些內容的空間。所以內容的實際寬度將會小於CSS設置的寬度。那麼在JavaScript中clientWidth
和clientHeight
時,就需要考慮到這一點。
但getComputedStyle(elem).width
有所不同。有些瀏覽器(比如Chrome瀏覽器)返回的是innerWidth
減去滾動條的寬度,另外有一些瀏覽器(比如Firefox),CSS的寬度會忽略滾動條的寬度。在這種瀏覽器中使用getComputedStyled
取出的寬度將沒什麼差異,也不需要依賴於前面介紹的幾何結構屬性。
注意:熟悉CSS的同學應該知道,元素的盒模型分為
content-box
和border-box
之類。那麼在JavaScript中,上述的這些屬性也略有不同。有關於這方面的方同之處,可以閱讀前期整理的學習筆記《視口寬高、位置與滾動高度》。
基於上面的相關信息,如果我們要獲取元素的位置,就可以依賴於上述的相關屬性封裝一個函數,比如getPosition()
:
function getPosition(el) { var xPos = 0; var yPos = 0; while (el) { if (el.tagName == "BODY") { // deal with browser quirks with body/window/document and page scroll var xScroll = el.scrollLeft || document.documentElement.scrollLeft; var yScroll = el.scrollTop || document.documentElement.scrollTop; xPos += (el.offsetLeft - xScroll + el.clientLeft); yPos += (el.offsetTop - yScroll + el.clientTop); } else { // for all other non-BODY elements xPos += (el.offsetLeft - el.scrollLeft + el.clientLeft); yPos += (el.offsetTop - el.scrollTop + el.clientTop); } el = el.offsetParent; } return { x: xPos, y: yPos };}
有關於上述代碼的詳細闡述,可以點擊這裡。
從編寫代碼的角度來說,找到元素的確切位置和獲取尺寸並不困難。但要搞清楚這些幾何屬性以及將這些屬性運用到實際的元素當中,還是有點棘手的。尤其是要考慮所有瀏覽器的情形以及瀏覽器怪異模式下。
JavaScript檢測元素有六個DOM的幾何結構屬性:offsetWidth
、offsetHeight
、clientWidth
、clientHeight
、scrollWidth
、scrollHeight
。再加上offsetTop
、offsetLeft
、scrollTop
、scrollLeft
、clientTop
和clientLeft
等方向距離的屬性。這樣一來,讓事情就變得複雜,對於像我這樣的初學者而言,極難理解,也易產生一些錯誤。正因這個原因,整理了一篇這樣的文章,因涉及的內容較多,有些零亂,加上是初學者,難免有不對之處,如果有不對之處,煩請各路大嬸拍正。
联系客服