打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
DOM系列:獲取元素位置和尺寸

對於每一位Web開發的同學而言,在CSS的世界當中,每一個元素都是一個盒子,都有描述盒子大小和位置的相關屬性。比如CSS的盒模型相關的屬性和position相關屬性。不過今天我們學習和聊的不是CSS的世界,而是來學習和聊JavaScript中怎麼獲取元素尺寸和位置。

在JavaScript中也有很多屬性允許我們讀取有關元素的widthheight和其他幾何特性的信息。對於元素的位置獲取和控制,在JavaScript中與CSS有所不同,移動或定位元素時,經常需要它們來正確地計算坐標。

在這篇教程中,將學習JavaScript如何獲取HTML元素的確切位置和大小,並瞭解它們的工作原理。

HTML中的佈局(位置)

在大多數情況下,元素的位置取決於其自身的CSS屬性,但在很大程度上取決於其父元素的CSS屬性。這裡所指的屬性主要是paddingmarginborder

比如下面這個簡單示例,示例中名為examplediv元素的盒模型視圖可以很好地顯示這些屬性如何影響佈局:

#example {    width: 300px;    height: 200px;    border: 25px solid #E8C48F;    padding: 20px;    overflow: auto;}

盒模型的視覺圖如下所示:

通過瀏覽器開發者工具可以很輕易的看到元素#example的盒模型視圖。視圖中清晰的表示了paddingmarginborder的值是如何表示的。而且在視圖中,可以看到每個CSS屬性,以及其對應的值。

#example元素它有borderpadding和滾動條(因為我們顯式設置了元素的widthheight,並且將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的盒模型視圖如下:

幾何結構

元素提供的widthheight和其他幾何結構的屬性,其值總是數字。它們被假定為以像素為單位。總體情況如下圖所示:

其實它有很多屬性,我們很難把它們都放在一張圖中,我在網上找了一張描述相對全一點的圖:

雖然這圖上的屬性,在CSS的世界中並沒有看過,但它們的值很簡單,也很容易理解。JavaScript就是通過這些屬性來獲取元素的位置或元素的尺寸。

讓我們從元素的外部開始探索它們。

offsetParent,offsetleft和offsetTop

offsetParentoffsetLeftoffsetTop三個屬性是“最外層”的幾何結構的屬性,因此我們從這幾個屬性著手開始學習。

**offsetParent**:返回一個指向最近的(closest,指包含層級上的最近)包含該元素的定位元素。如果沒有定位的元素,則offsetParent為最近的table元素對象或根元素(標準模式下為<html>元素,怪異模式下為<body>元素)。當元素的style.display設置為nonepositionfixed時,offsetParent返回null

在大多數實際情況下,可以使用offsetParent獲取最近的CSS位置(CSS-Positioned)的祖先。而其中offsetleftoffsetTop提供相對於左上角的xy坐標。

比如下面這個示例,內部的<div>元素具有<main>作為offsetParent,而offsetLeftoffsetTop從左上角移動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()輸出的結果可以看到,元素#exampleoffsetParentmain元素。通過offsetLeftoffsetTop輸出的值為180

特別注意,有幾種情況之下,offsetParent返回的值為null

  • 不可見的元素(比如元素設置display:none或元素就不在document中)
  • <body><html>元素
  • position:fixed的元素

從上圖中大家對offsetLeftoffsetTop或許有所感知,但還是花一點篇幅來描述:

  • **offsetLeft**:當前元素左上角相對於offsetParent 節點的左邊界偏移的像素值。對塊級元素來說,offsetTopoffsetLeftoffsetWidthoffsetHeight 描述了元素相對於 offsetParent 的邊界框。然而,對於可被截斷到下一行的行內元素(如 span),offsetTopoffsetLeft 描述的是第一個邊界框的位置(使用 Element.getClientRects() 來獲取其寬度和高度),而 offsetWidthoffsetHeight 描述的是邊界框的尺寸(使用 Element.getBoundingClientRect 來獲取其位置)。因此,使用 offsetLeftoffsetTopoffsetWidthoffsetHeight 來對應 lefttopwidthheight 的一個盒子將不會是文本容器 span 的盒子邊界。
  • **offsetTop**:當前元素相對於其 offsetParent 元素的頂部的距離。

事實上,每個元素的父元素都有一個相似的偏移量值,所以我們的循環只是將它們加起來,直到沒有父元素為止。最終的結果是所有偏移量的總和,以幫助我們為元素獲取一個精確的位置。該位置考慮了marginpaddingtopleft相關的值。但有一件事情是遺漏了的。

偏移量屬性沒有考慮的一個值是元素的border。原因是,border被認為是內部元素的左上角的一部分,但是它的大小對某些東西的位置有影響。為了測量border的大小,我們使用clientLeftclientTop來獲取。這兩個屬性我們後面會介紹。

offsetWidth和offsetHeight

上面我們看了元素外部影響元素的相關屬性。現在我們來看看元素自身相關的屬性。主要有offsetWidthoffsetHeight

  • offsetWidth:一個元素的佈局寬度。offsetWidth是測量包含元素的邊框、水平線上的內邊距、豎直方向滾動條以及CSS設置的寬度的值。
  • offsetHeight:元素的像素高度,高度包含該元素的垂直內邊距和邊框,且是一個整數。通常,元素的offsetHeight是一種元素CSS高度的衡量標準,包括元素的邊框、內邊距和元素的水平滾動條(如果存在且渲染的話),不包含:before:after等偽類元素的高度。對於文檔的body對象,它包括代替元素的CSS高度線性總含量高。浮動元素的向下延伸內容高度是被忽略的。

offsetWidthoffsetHeight提供元素的“外部”的寬度和高度。換句話說,包括border在內的全部大小。

為了更易於理解,還是用張圖來闡述:

在實際使用之中,不顯示的元素其幾何結構屬性的值為0null。也就是說,幾何結構屬性只對可見的元素進行計算

如果一個元素(或它的任何一個祖先元素)的display:none或不在文檔中,那麼所有的幾何屬性都為0null。那麼問題來了,我們應該怎麼去判斷呢?為瞭解決這個問題我們可以寫一個函數。比如:

function isHidden(elem) {    return !elem.offsetWidth && !elem.offsetHeight;}

請注意,對於屏幕上的元素,isHidden返回的值是true,但是沒有任何大小,比如一個空的<div>

clientTop和clientLeft

前面提到過offsetLeftoffsetTop沒有考慮border。為了測量border的大小,我們使用clientLeftclientTop來獲取。接下來我們來瞭解和學習clientTopclientLeft

比如上面的示例:

  • border-left的寬度:clientLeft = 25
  • border-top的寬度:clientTop = 25

但準確地說,它們不是border,而是內部與外部的相對坐標。那麼有什麼區別呢?

比如這樣的一個示例,當文檔是從右到左排版(操作系統是阿拉伯語或希伯來語)時,也就是directionrtl,它就可見了。然後滾動條不在右邊,而是在左邊,這個時候clientLeft的值也包含了滾動條的寬度。

基於上面的示例,如果direction:rtl,這個時候clientLeft的值就變成了25 + 16 = 41

clientWidth 和 clientHeight

clientWidthclientHeight屬性可以用來獲取元素邊框內區域的大小。它們包括了內容的寬度和padding,但不包含滾動條寬度:

先來看上圖是有關於clientHeight的值,因為它更容易。主要是沒有水平滾動條,所以它正好是邊框內區域的總和:height + padding-top + padding-bottom,即:200 + 2 * 20 = 240px

再來看clientWidth。這裡的內容寬度不是300px,而是284px,因為還有一個16px寬的滾動條。所以clientWidth的值是284 + 20 * 2 = 324px

如果沒有padding,那麼clientWidthclientHeight就是border和滾動內的內容區域寬度和高度。

也就是說,當沒有padding時,clientWidthclientHeight可以用來獲取內容區域的widthheight

現在,使用offset*client*屬性,我們已經考慮了paddingmarginbordertopleft相關的屬性。在99%的情況之下,這應該是我們需要考慮的全部,除非你的運用場景是剩下的那1%部分。

很多時候,元素有可能是位於帶有滾動條的容器之中。如果是這樣的話,為了確保能準確的獲取到元素的位置,還需要通過scrollLeftscrollTop屬性來進行測量,並從總數中減去它們。

scrollLeft 和 scrollTop

scrollLeftscrollTop屬性可以獲取或設置滾動元素隱藏部分的寬度和高度。

scrollLeft可以讀取或設置元素滾動條到元素左邊的距離。如果這個元素的內容排列方向(direction) 是rtl (right-to-left) ,那麼滾動條會位於最右側(內容開始處),並且scrollLeft值為0。此時,當你從右到左拖動滾動條時,scrollLeft會從0變為負數。

scrollTop可以獲取或設置一個元素的內容垂直滾動的像素數。 一個元素的 scrollTop 值是這個元素的頂部到它的最頂部可見內容(的頂部)的距離的度量。當一個元素的內容沒有產生垂直方向的滾動條,那麼它的 scrollTop 值為0

在下面的圖片中,我們可以看到在一個塊中垂直滾動條的scrollHeightscrollTop

大多數幾何結構屬性都是只讀的,但是scrollLeftscrollTop是可更改的。如果將scrollTop設置為0Infinity將會使元素分別滾動到瀏覽器的最頂端和最底端。

scrollWidth和scrollHeight

clientWidthclientHeight僅負責元素的可見部分。而屬性scrollWidthscrollHeight還會包括不可見(隱藏)的部分。

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

不要從CSS中獲取寬度和高度

前面介紹的一些屬性都是有關於DOM元素的幾何結構屬性。在JavaScript中通常用來計算寬度、高度和距離。但在“樣式和類”一節中學習中我們知道,在JavaScript中可以使用getComputedStyle來讀取CSS中的widthheight

既然如此,那麼為什麼不這樣來讀取元素的寬度呢?

let elem = document.body;console.log( getComputedStyle(elem).width );

其實我們使用幾何結構屬性,其實是有原因所在的:

首先,CSS的widthheight取決於另一個屬性:box-sizing。在CSS中,這個屬性定義了元素的寬度和高度。換句話說,CSS可以改變元素盒模型的大小,而這種改變有可能會破壞JavaScript的幾何結構屬性取得的值。

其次,CSS的寬度和高度可以是auto,例如一個內聯元素span

還有一個原因,就是滾動條。因為滾動條會佔用一些內容的空間。所以內容的實際寬度將會小於CSS設置的寬度。那麼在JavaScript中clientWidthclientHeight時,就需要考慮到這一點。

getComputedStyle(elem).width有所不同。有些瀏覽器(比如Chrome瀏覽器)返回的是innerWidth減去滾動條的寬度,另外有一些瀏覽器(比如Firefox),CSS的寬度會忽略滾動條的寬度。在這種瀏覽器中使用getComputedStyled取出的寬度將沒什麼差異,也不需要依賴於前面介紹的幾何結構屬性。

注意:熟悉CSS的同學應該知道,元素的盒模型分為content-boxborder-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的幾何結構屬性:offsetWidthoffsetHeightclientWidthclientHeightscrollWidthscrollHeight。再加上offsetTopoffsetLeftscrollTopscrollLeftclientTopclientLeft等方向距離的屬性。這樣一來,讓事情就變得複雜,對於像我這樣的初學者而言,極難理解,也易產生一些錯誤。正因這個原因,整理了一篇這樣的文章,因涉及的內容較多,有些零亂,加上是初學者,難免有不對之處,如果有不對之處,煩請各路大嬸拍正。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
offset、client、scroll开头的属性归纳总结
JavaScript获取DOM元素位置和尺寸大小
用Javascript获取页面元素的位置
JS中的offsetWidth/offsetHeight/offsetTop/offsetLeft、clientWidth/clientHeight/clientTop/clientLeft、scrollWidth/scrollHeight/scrollTop/scrollLeft
Javascript拖拽&拖放系列文章2之offsetLeft、offsetTop、offsetWidth、offsetHeight属性
javascript好文
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服