Javascript 元素大小和滾動(dòng)

2023-02-17 10:54 更新

JavaScript 中有許多屬性可讓我們讀取有關(guān)元素寬度、高度和其他幾何特征的信息。

我們?cè)?JavaScript 中移動(dòng)或定位元素時(shí),我們會(huì)經(jīng)常需要它們。

示例元素

作為演示屬性的示例元素,我們將使用下面給出的元素:

<div id="example">
  ...Text...
</div>
<style>
  #example {
    width: 300px;
    height: 200px;
    border: 25px solid #E8C48F;
    padding: 20px;
    overflow: auto;
  }
</style>

它有邊框(border),內(nèi)邊距(padding)和滾動(dòng)(scrolling)等全套功能。但沒有外邊距(margin),因?yàn)樗鼈儾皇窃乇旧淼囊徊糠郑⑶宜鼈儧]什么特殊的屬性。

這個(gè)元素看起來就像這樣:


你可以 在 sandbox 中打開這個(gè)文檔。

注意滾動(dòng)條

上圖演示了元素具有滾動(dòng)條這種最復(fù)雜的情況。一些瀏覽器(并非全部)通過從內(nèi)容(上面標(biāo)記為 “content width”)中獲取空間來為滾動(dòng)條保留空間。

因此,如果沒有滾動(dòng)條,內(nèi)容寬度將是 300 px,但是如果滾動(dòng)條寬度是 16px(不同的設(shè)備和瀏覽器,滾動(dòng)條的寬度可能有所不同),那么還剩下 300 - 16 = 284px,我們應(yīng)該考慮到這一點(diǎn)。這就是為什么本章的例子總是假設(shè)有滾動(dòng)條。如果沒有滾動(dòng)條,一些計(jì)算會(huì)更簡(jiǎn)單。

文本可能會(huì)溢出到 ?padding-bottom? 中

在我們的插圖中的 padding 中通常顯示為空,但是如果元素中有很多文本,并且溢出了,那么瀏覽器會(huì)在 padding-bottom 處顯示“溢出”文本,這是正?,F(xiàn)象。

幾何

這是帶有幾何屬性的整體圖片:


這些屬性的值在技術(shù)上講是數(shù)字,但這些數(shù)字其實(shí)是“像素(pixel)”,因此它們是像素測(cè)量值。

讓我們從元素外部開始探索屬性。

offsetParent,offsetLeft/Top

這些屬性很少使用,但它們?nèi)匀皇恰白钔饷妗钡膸缀螌傩?,所以我們將從它們開始。

offsetParent 是最接近的祖先(ancestor),在瀏覽器渲染期間,它被用于計(jì)算坐標(biāo)。

最近的祖先為下列之一:

  1. CSS 定位的(?position? 為 ?absolute?、?relative?、fixed 或 ?sticky?),
  2. 或 ?<td>?,?<th>?,?<table>?,
  3. 或 ?<body>?。

屬性 offsetLeft/offsetTop 提供相對(duì)于 offsetParent 左上角的 x/y 坐標(biāo)。

在下面這個(gè)例子中,內(nèi)部的 <div> 有 <main> 作為 offsetParent,并且 offsetLeft/offsetTop 讓它從左上角位移(180):

<main style="position: relative" id="main">
  <article>
    <div id="example" style="position: absolute; left: 180px; top: 180px">...</div>
  </article>
</main>
<script>
  alert(example.offsetParent.id); // main
  alert(example.offsetLeft); // 180(注意:這是一個(gè)數(shù)字,不是字符串 "180px")
  alert(example.offsetTop); // 180
</script>


有以下幾種情況下,offsetParent 的值為 null

  1. 對(duì)于未顯示的元素(?display:none? 或者不在文檔中)。
  2. 對(duì)于 ?<body>? 與 ?<html>?。
  3. 對(duì)于帶有 ?position:fixed? 的元素。

offsetWidth/Height

現(xiàn)在,讓我們繼續(xù)關(guān)注元素本身。

這兩個(gè)屬性是最簡(jiǎn)單的。它們提供了元素的“外部” width/height?;蛘?,換句話說,它的完整大?。òㄟ吙颍?/p>


對(duì)于我們的示例元素:

  • ?offsetWidth = 390? —— 外部寬度(width),可以計(jì)算為內(nèi)部 CSS-width(?300px?)加上 padding(?2 * 20px?)和 border(?2 * 25px?)。
  • ?offsetHeight = 290? —— 外部高度(height)。

對(duì)于未顯示的元素,幾何屬性為 0/null

僅針對(duì)顯示的元素計(jì)算幾何屬性。

如果一個(gè)元素(或其任何祖先)具有 display:none 或不在文檔中,則所有幾何屬性均為零(或 offsetParent 為 null)。

例如,當(dāng)我們創(chuàng)建了一個(gè)元素,但尚未將其插入文檔中,或者它(或它的祖先)具有 display:none 時(shí),offsetParent 為 null,并且 offsetWidth 和 offsetHeight 為 0。

我們可以用它來檢查一個(gè)元素是否被隱藏,像這樣:

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

請(qǐng)注意,對(duì)于會(huì)展示在屏幕上,但大小為零的元素,它們的 isHidden 返回 true。

clientTop/Left

在元素內(nèi)部,我們有邊框(border)。

為了測(cè)量它們,可以使用 clientTop 和 clientLeft

在我們的例子中:

  • ?clientLeft = 25? —— 左邊框?qū)挾?/li>
  • ?clientTop = 25? —— 上邊框?qū)挾?/li>


……但準(zhǔn)確地說 —— 這些屬性不是邊框的 width/height,而是內(nèi)側(cè)與外側(cè)的相對(duì)坐標(biāo)。

有什么區(qū)別?

當(dāng)文檔從右到左顯示(操作系統(tǒng)為阿拉伯語(yǔ)或希伯來語(yǔ))時(shí),影響就顯現(xiàn)出來了。此時(shí)滾動(dòng)條不在右邊,而是在左邊,此時(shí) clientLeft 則包含了滾動(dòng)條的寬度。

在這種情況下,clientLeft 的值將不是 25,而是加上滾動(dòng)條的寬度 25 + 16 = 41。

這是希伯來語(yǔ)的例子:


clientWidth/Height

這些屬性提供了元素邊框內(nèi)區(qū)域的大小。

它們包括了 “content width” 和 “padding”,但不包括滾動(dòng)條寬度(scrollbar):


在上圖中,我們首先考慮 clientHeight。

這里沒有水平滾動(dòng)條,所以它恰好是 border 內(nèi)的總和:CSS-height 200px 加上頂部和底部的 padding(2 * 20px),總計(jì) 240px。

現(xiàn)在 clientWidth —— 這里的 “content width” 不是 300px,而是 284px,因?yàn)楸粷L動(dòng)條占用了 16px。所以加起來就是 284px 加上左側(cè)和右側(cè)的 padding,總計(jì) 324px

如果這里沒有 padding,那么 clientWidth/Height 代表的就是內(nèi)容區(qū)域,即 border 和 scrollbar(如果有)內(nèi)的區(qū)域。


因此,當(dāng)沒有 padding 時(shí),我們可以使用 clientWidth/clientHeight 來獲取內(nèi)容區(qū)域的大小。

scrollWidth/Height

這些屬性就像 clientWidth/clientHeight,但它們還包括滾動(dòng)出(隱藏)的部分:


在上圖中:

  • ?scrollHeight = 723? —— 是內(nèi)容區(qū)域的完整內(nèi)部高度,包括滾動(dòng)出的部分。
  • ?scrollWidth = 324? —— 是完整的內(nèi)部寬度,這里我們沒有水平滾動(dòng),因此它等于 ?clientWidth?。

我們可以使用這些屬性將元素展開(expand)到整個(gè) width/height。

像這樣:

// 將元素展開(expand)到完整的內(nèi)容高度
element.style.height = `${element.scrollHeight}px`;


scrollLeft/scrollTop

屬性 scrollLeft/scrollTop 是元素的隱藏、滾動(dòng)部分的 width/height。

在下圖中,我們可以看到帶有垂直滾動(dòng)塊的 scrollHeight 和 scrollTop


換句話說,scrollTop 就是“已經(jīng)滾動(dòng)了多少”。

?scrollLeft/scrollTop? 是可修改的

大多數(shù)幾何屬性是只讀的,但是 scrollLeft/scrollTop 是可修改的,并且瀏覽器會(huì)滾動(dòng)該元素。

如果你點(diǎn)擊下面的元素,則會(huì)執(zhí)行代碼 elem.scrollTop += 10。這使得元素內(nèi)容向下滾動(dòng) 10px。


將 scrollTop 設(shè)置為 0 或一個(gè)大的值,例如 1e9,將會(huì)使元素滾動(dòng)到頂部/底部。

不要從 CSS 中獲取 width/height

我們剛剛介紹了 DOM 元素的幾何屬性,它們可用于獲得寬度、高度和計(jì)算距離。

但是,正如我們?cè)?nbsp;樣式和類 一章所知道的那樣,我們可以使用 getComputedStyle 來讀取 CSS-width 和 height。

那為什么不像這樣用 getComputedStyle 讀取元素的 width 呢?

let elem = document.body;

alert( getComputedStyle(elem).width ); // 顯示 elem 的 CSS width

為什么我們應(yīng)該使用幾何屬性呢?這里有兩個(gè)原因:

  1. 首先,CSS ?width/height? 取決于另一個(gè)屬性:?box-sizing?,它定義了“什么是” CSS 寬度和高度。出于 CSS 的目的而對(duì) ?box-sizing? 進(jìn)行的更改可能會(huì)破壞此類 JavaScript 操作。
  2. 其次,CSS 的 ?width/height? 可能是 ?auto?,例如內(nèi)聯(lián)(inline)元素:
  3. <span id="elem">Hello!</span>
    
    <script>
      alert( getComputedStyle(elem).width ); // auto
    </script>

    從 CSS 的觀點(diǎn)來看,width:auto 是完全正常的,但在 JavaScript 中,我們需要一個(gè)確切的 px 大小,以便我們?cè)谟?jì)算中使用它。因此,這里的 CSS 寬度沒什么用。

還有另一個(gè)原因:滾動(dòng)條。有時(shí),在沒有滾動(dòng)條的情況下代碼工作正常,當(dāng)出現(xiàn)滾動(dòng)條時(shí),代碼就出現(xiàn)了 bug,因?yàn)樵谀承g覽器中,滾動(dòng)條會(huì)占用內(nèi)容的空間。因此,可用于內(nèi)容的實(shí)際寬度小于 CSS 寬度。而 clientWidth/clientHeight 則會(huì)考慮到這一點(diǎn)。

……但是,使用 getComputedStyle(elem).width 時(shí),情況就不同了。某些瀏覽器(例如 Chrome)返回的是實(shí)際內(nèi)部寬度減去滾動(dòng)條寬度,而某些瀏覽器(例如 Firefox)返回的是 CSS 寬度(忽略了滾動(dòng)條)。這種跨瀏覽器的差異是不使用 getComputedStyle 而依靠幾何屬性的原因。

如果你的瀏覽器保留了滾動(dòng)條的空間(大多數(shù) Windows 中的瀏覽器),那么你可以在下面測(cè)試它。

https://zh.js.cx/article/size-and-scroll/cssWidthScroll/

帶有文本的元素具有 width:300px。

在桌面 Windows 操作系統(tǒng)上,F(xiàn)irefox、Chrome、Edge 都為滾動(dòng)條保留了空間。但 Firefox 顯示的是 300px,而 Chrome 和 Edge 顯示較少。這是因?yàn)?Firefox 返回 CSS 寬度,其他瀏覽器返回“真實(shí)”寬度。

請(qǐng)注意,所描述的差異只是關(guān)于從 JavaScript 讀取的 getComputedStyle(...).width,而視覺上看,一切都是正確的。

總結(jié)

元素具有以下幾何屬性:

  • ?offsetParent? —— 是最接近的 CSS 定位的祖先,或者是 ?td?,?th?,?table?,?body?。
  • ?offsetLeft/offsetTop? —— 是相對(duì)于 ?offsetParent? 的左上角邊緣的坐標(biāo)。
  • ?offsetWidth/offsetHeight? —— 元素的“外部” width/height,邊框(border)尺寸計(jì)算在內(nèi)。
  • ?clientLeft/clientTop? —— 從元素左上角外角到左上角內(nèi)角的距離。對(duì)于從左到右顯示內(nèi)容的操作系統(tǒng)來說,它們始終是左側(cè)/頂部 border 的寬度。而對(duì)于從右到左顯示內(nèi)容的操作系統(tǒng)來說,垂直滾動(dòng)條在左邊,所以 ?clientLeft? 也包括滾動(dòng)條的寬度。
  • ?clientWidth/clientHeight? —— 內(nèi)容的 width/height,包括 padding,但不包括滾動(dòng)條(scrollbar)。
  • ?scrollWidth/scrollHeight? —— 內(nèi)容的 width/height,就像 ?clientWidth/clientHeight? 一樣,但還包括元素的滾動(dòng)出的不可見的部分。
  • ?scrollLeft/scrollTop? —— 從元素的左上角開始,滾動(dòng)出元素的上半部分的 width/height。

除了 scrollLeft/scrollTop 外,所有屬性都是只讀的。如果我們修改 scrollLeft/scrollTop,瀏覽器會(huì)滾動(dòng)對(duì)應(yīng)的元素。

任務(wù)


相對(duì)于底部滾動(dòng)了多少?

重要程度: 5

elem.scrollTop 屬性是從頂部滾動(dòng)出來的部分的大小。如何獲得底部滾動(dòng)的大?。ㄎ覀兎Q其為 scrollBottom)?

編寫適用于任意 elem 的代碼。

P.S. 請(qǐng)檢查你的代碼:如果沒有滾動(dòng),或元素底部已經(jīng)完全滾動(dòng)完成,那么它應(yīng)該返回 0


解決方案

解決方案:

let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight;

換句話說:(完全高度)減去(已滾出頂部的高度)減去(可見部分的高度)—— 得到的結(jié)果就是滾動(dòng)出來的底部的部分。


滾動(dòng)條的寬度是多少?

重要程度: 3

編寫代碼,返回標(biāo)準(zhǔn)滾動(dòng)條寬度。

對(duì)于 Windows,它通常在 12px 和 20px 之間變化。如果瀏覽器沒有為其保留任何空間(滾動(dòng)條以半透明的形式處于文本上面,也是可能發(fā)生的),那么它可能是 0px。

P.S. 該代碼應(yīng)適用于任何 HTML 文檔,而不依賴于其內(nèi)容。


解決方案

為了獲得滾動(dòng)條的寬度,我們可以創(chuàng)建一個(gè)帶有滾動(dòng)條的元素,但是沒有邊框(border)和內(nèi)邊距(padding)。

然后,它的全寬度 offsetWidth 和內(nèi)部?jī)?nèi)容寬度 clientWidth 之間的差值就是滾動(dòng)條的寬度:

// 創(chuàng)建一個(gè)包含滾動(dòng)條的 div
let div = document.createElement('div');

div.style.overflowY = 'scroll';
div.style.width = '50px';
div.style.height = '50px';

// 必須將其放入文檔(document)中,否則其大小將為 0
document.body.append(div);
let scrollWidth = div.offsetWidth - div.clientWidth;

div.remove();

alert(scrollWidth);

將小球置于區(qū)域(field)中心

重要程度: 5

源文件的效果如下:


區(qū)域(field)的中心坐標(biāo)是多少?

計(jì)算它們,并將小球置于綠色的區(qū)域(field)中心:


  • 該元素應(yīng)該通過 JavaScript 移動(dòng),而不是 CSS。
  • 該代碼應(yīng)該適用于任何大小的球(?10?、?20?、?30? 像素)以及任意大小的區(qū)域(field),而不應(yīng)該綁定到給定值。

P.S. 當(dāng)然了,置于中心的操作通過 CSS 也可以完成,但是這里我們需要通過 JavaScript 完成。此外,當(dāng)必須使用 JavaScript 時(shí),我們可能會(huì)遇到其他話題以及更加復(fù)雜的情況,這里我們只是做一個(gè)“熱身”。

打開一個(gè)任務(wù)沙箱。


解決方案

球具有 position:absolute。這意味著它的 left/top 坐標(biāo)是從最近的具有定位屬性的元素開始測(cè)量的,這個(gè)元素即 #field(因?yàn)樗?nbsp;position:relative)。

坐標(biāo)從場(chǎng)(field)的左上角內(nèi)側(cè)開始:


內(nèi)部的場(chǎng)(field)的 width/height 是 clientWidth/clientHeight。所以場(chǎng)(field)的中心坐標(biāo)為 (clientWidth/2, clientHeight/2)。

……但是,如果我們將 ball.style.left/top 設(shè)置為這種值,那么在中心的會(huì)是球的左上邊緣,而不是整個(gè)球:

ball.style.left = Math.round(field.clientWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2) + 'px';

這是它將顯示出來的效果:


為了使球的中心與場(chǎng)(field)的中心重合,我們應(yīng)該把球向左移動(dòng)球?qū)挾鹊囊话?,并向上移?dòng)球高度的一半:

ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px';

現(xiàn)在,球終于居中了。

注意:陷阱!

當(dāng) <img> 沒有 width/height 時(shí),代碼將無法可靠地工作:

<img src="ball.png" id="ball">

當(dāng)瀏覽器不知道圖片的 width/height(通過標(biāo)簽 attribute 或 CSS)時(shí),它會(huì)假定它們等于 0,直到圖片加載完成。

因此,在圖片加載完成之前,ball.offsetWidth 的值為 0。這會(huì)導(dǎo)致上面的代碼中會(huì)有錯(cuò)誤的坐標(biāo)。

在第一次加載完成后,瀏覽器通常會(huì)緩存該圖片,并在下一次加載時(shí),瀏覽器會(huì)立即擁有該圖片的大小。但是在第一次加載時(shí),ball.offsetWidth 的值為 0。

我們應(yīng)該通過在 <img> 中添加 width/height 來解決這個(gè)問題:

<img src="ball.png" width="40" height="40" id="ball">

……或者在 CSS 中提供大小:

#ball {
  width: 40px;
  height: 40px;
}

使用沙箱打開解決方案。


CSS width 與 clientWidth 的不同點(diǎn)

重要程度: 5

getComputedStyle(elem).width 與 elem.clientWidth 之間有什么不同點(diǎn)?

指出至少三種不同點(diǎn)。當(dāng)然越多越好。


解決方案

不同點(diǎn):

  1. ?clientWidth? 值是數(shù)值,而 ?getComputedStyle(elem).width? 返回一個(gè)以 ?px? 作為后綴的字符串。
  2. ?getComputedStyle? 可能會(huì)返回非數(shù)值的 width,例如內(nèi)聯(lián)(inline)元素的 ?"auto"?。
  3. ?clientWidth? 是元素的內(nèi)部?jī)?nèi)容區(qū)域加上 padding,而 CSS width(具有標(biāo)準(zhǔn)的 ?box-sizing?)是內(nèi)部?jī)?nèi)容區(qū)域,不包括 padding。
  4. 如果有滾動(dòng)條,并且瀏覽器為其保留了空間,那么某些瀏覽器會(huì)從 CSS width 中減去該空間(因?yàn)樗辉倏捎糜趦?nèi)容),而有些則不會(huì)這樣做。?clientWidth? 屬性總是相同的:如果為滾動(dòng)條保留了空間,那么將減去滾動(dòng)條的大小。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)