一篇文章讓你徹底搞懂js中的位置計算
引言
文章中涉及到的api列表:
- scroll相關(guān)Api
- client相關(guān)Api
- offset相關(guān)Api
- Element.getBoundingClientRectAPi
- Window.getComputedStyleApi
我們會結(jié)合api定義,知名開源庫中的應(yīng)用場景來逐層分析這些api。足以應(yīng)對工作中關(guān)于元素位置計算的大部分場景。
注意在使用位置計算api時要格外的小心,不合理的使用他們可能會造成布局抖動Layout Thrashing影響頁面渲染。
scroll
首先我們先來看看scroll相關(guān)的屬性和方法。
Element.scroll()
Element.scroll()方法是用于在給定的元素中滾動到某個特定坐標(biāo)的Element 接口。
element.scroll(x-coord, y-coord) element.scroll(options)
- x-coord 是指在元素左上方區(qū)域橫軸方向上想要顯示的像素。
- y-coord 是指在元素左上方區(qū)域縱軸方向上想要顯示的像素。
也就是element.scroll(x,y)會將元素滾動條位置滾動到對應(yīng)x,y的位置。
同時也支持element.scroll(options)方式調(diào)用,支持傳入額外的配置:
{
left: number,
top: number,
behavior: 'smooth' | 'auto' // 平滑滾動還是默認(rèn)直接滾動
}
Element.scrollHeight/scrollWidth
Element.scrollHeight 這個只讀屬性是一個元素內(nèi)容高度的度量,包括由于溢出導(dǎo)致的視圖中不可見內(nèi)容。
scrollHeight 的值等于該元素在不使用滾動條的情況下為了適應(yīng)視口中所用內(nèi)容所需的最小高度。 沒有垂直滾動條的情況下,scrollHeight值與元素視圖填充所有內(nèi)容所需要的最小值clientHeight相同。包括元素的padding,但不包括元素的border和margin。scrollHeight也包括 ::before 和 ::after這樣的偽元素。
換句話說Element.scrollHeight在元素不存在滾動條的情況下是恒等于clientHeight的。
但是如果出現(xiàn)了滾動條的話scrollHeight指的是包含元素不可以見內(nèi)容的高度,出現(xiàn)滾動條的情況下是scrollHeight恒大于clientHeight。
- Element.scrollWidth 這也是一個元素內(nèi)容寬度的只讀屬性,包含由于溢出導(dǎo)致視圖中不可以見的內(nèi)容。
原理上和scrollHeight是同理的,只不過這里是寬度而非高度。
簡單來說一個元素如果不存在滾動條,那么他們的scroll和client都是相等的值。如果存在了滾動條,client只會計算出當(dāng)前元素展示出來的高度/寬度,而scroll不僅僅會計算當(dāng)前元素展示出的,還會包含當(dāng)前元素的滾動條隱藏內(nèi)容的高度/寬度。
clientWidth/height + [滾動條被隱藏內(nèi)容寬度/高度] = scrollWidth/Height
Element.scrollLeft/scrollTop
- Element.scrollTop 屬性可以獲取或設(shè)置一個元素的內(nèi)容垂直滾動的像素數(shù).
- Element.scrollLeft 屬性可以讀取或設(shè)置元素滾動條到元素左邊的距離.
需要額外注意的是: 注意如果這個元素的內(nèi)容排列方向(direction) 是rtl (right-to-left) ,那么滾動條會位于最右側(cè)(內(nèi)容開始處),并且scrollLeft值為0。此時,當(dāng)你從右到左拖動滾動條時,scrollLeft會從0變?yōu)樨?fù)數(shù)。
scrollLeft/Top在日常工作中是比較頻繁使用關(guān)于操作滾動條的相關(guān)api,他們是一個可以設(shè)置的值。根據(jù)不同的值對應(yīng)可以控制滾動條的位置。
其實這兩個屬性和上方的Element.scroll()可以達(dá)到相同的效果。
在實際工作中如果對于滾動操作有很頻繁的需求,個人建議去使用better-scroll,它是一個移動/web端的通用js滾動庫,內(nèi)部是基于元素transform去操作的滾動并不會觸發(fā)相關(guān)重塑/回流。
判斷當(dāng)前元素是否存在滾動條
出現(xiàn)滾動條便意味著元素空間將大于其內(nèi)容顯示區(qū)域,根據(jù)這個現(xiàn)象便可以得到判斷是否出現(xiàn)滾動條的規(guī)則。
export const hasScrolled = (element, direction) => {
if (!element || element.nodeType !== 1) return;
if (direction === "vertical") {
return element.scrollHeight > element.clientHeight;
} else if (direction === "horizontal") {
return element.scrollWidth > element.clientWidth;
}
};
判斷用戶是否滾動到底部
本質(zhì)上就是當(dāng)元素出現(xiàn)滾動條時,判斷當(dāng)前元素出現(xiàn)的高度 + 滾動條高度 = 元素本身的高度(包含隱藏部分)。
element.scrollHeight - element.scrollTop === element.clientHeight
client
MouseEvent.clientX/Y
MounseEvent.clientX/Y同樣也是只讀屬性,它提供事件發(fā)生時的應(yīng)用客戶端區(qū)域的水平坐標(biāo)。
例如,不論頁面是否有垂直/水平滾動,當(dāng)你點擊客戶端區(qū)域的左上角時,鼠標(biāo)事件的 clientX/Y 值都將為 0 。
其實MouseEvent.clientX/Y也就是相對于當(dāng)前視口(瀏覽器可視區(qū))進(jìn)行位置計算。
轉(zhuǎn)載一張非常直白的圖:

Element.clientHeight/clientWidth
Element.clientWidth/clinetHeight 屬性表示元素的內(nèi)部寬度,以像素計。該屬性包括內(nèi)邊距 padding,但不包括邊框 border、外邊距 margin 和垂直滾動條(如果有的話)。
內(nèi)聯(lián)元素以及沒有 CSS 樣式的元素的 clientWidth 屬性值為 0。
在不出現(xiàn)滾動條時候Element.clientWidth/Height === Element.scrollWidth/Height

Element.clientTop/clientLeft
Element.clientLeft表示一個元素的左邊框的寬度,以像素表示。如果元素的文本方向是從右向左(RTL, right-to-left),并且由于內(nèi)容溢出導(dǎo)致左邊出現(xiàn)了一個垂直滾動條,則該屬性包括滾動條的寬度。clientLeft 不包括左外邊距和左內(nèi)邊距。clientLeft 是只讀的。
同樣的Element.clientTop表示元素上邊框的寬度,也是一個只讀屬性。
這兩個屬性日常使用會比較少,但是也應(yīng)該了解以避免搞混這些看似名稱都類似的屬性。
offset
MouseEvent.offsetX/offsetY
MouseEvent 接口的只讀屬性 offsetX/Y 規(guī)定了事件對象與目標(biāo)節(jié)點的內(nèi)填充邊(padding edge)在 X/Y 軸方向上的偏移量。
相信使用過offest的同學(xué)對這個屬性深有體會,它是相對于父元素的左邊/上方的偏移量。
注意是觸發(fā)元素也就是 e.target,額外小心如果事件對象中存在從一個子元素當(dāng)移動到子元素內(nèi)部時,e.offsetX/Y 此時相對于子元素的左上角偏移量。
offsetWidth/offsetHeight
HTMLElement.offsetWidth/Height 是一個只讀屬性,返回一個元素的布局寬度/高度。
所謂的布局寬度也就是相對于我們上邊說到的clientHeight/Width,offsetHeight/Width,他們都是不包含border以及滾動條的寬/高(如果存在的話)。
而offsetWidth/offsetHeight返回元素的布局寬度/高度,包含元素的邊框(border)、水平線/垂直線上的內(nèi)邊距(padding)、豎直/水平方向滾動條(scrollbar)(如果存在的話)、以及CSS設(shè)置的寬度(width)的值。
offsetTop/left
HTMLElement.offsetLeft 是一個只讀屬性,返回當(dāng)前元素左上角相對于 HTMLElement.offsetParent 節(jié)點的左邊界偏移的像素值。
注意返回的是相對于 HTMLElement.offsetParent 節(jié)點左邊邊界的偏移量。
何為HTMLElement.offsetParent?
HTMLElement.offsetParent 是一個只讀屬性,返回一個指向最近的(指包含層級上的最近)包含該元素的定位元素或者最近的 table,td,th,body 元素。當(dāng)元素的 style.display 設(shè)置為 "none" 時,offsetParent 返回 null。offsetParent 很有用,因為 offsetTop 和 offsetLeft 都是相對于其內(nèi)邊距邊界的。 -- MDN
講講人話,當(dāng)前元素的祖先組件節(jié)點如果不存在任何 table,td,th 以及 position 屬性為 relative,absolute 等為定位元素時,offsetLeft/offsetTop 返回的是距離 body 左/上角的偏移量。
當(dāng)祖先元素中有定位元素(或者上述標(biāo)簽元素)時,它就可以被稱為元素的offsetParent。元素的 offsetLeft/offsetTop 的值等于它的左邊框左側(cè)/頂邊框頂部到它的 offsetParent 元素左邊框的距離。
我們來看看這張圖:

計算元素距離 body 的偏移量
當(dāng)我們需要獲得元素距離 body 的距離時,但是又無法確定父元素是否存在定位元素時(大多數(shù)時候在組件開發(fā)中,并不清楚父節(jié)點是否存在定位)。此時需要實現(xiàn)類似 jqery 的 offset()方法:獲得當(dāng)前元素對于 body 的偏移量。
- 無法直接使用 offsetLeft/offsetTop 獲取,因為并不確定父元素是否存在定位元素。
- 使用遞歸解決,累加偏移量 offset,當(dāng)前 offsetParent 不為 body 時。
- 繼續(xù)遞歸向上超著 offsetParent 累加 offset,直到遇到 body 元素停止。
const getOffsetSize = function(Node: any, offset?: any): any {
if (!offset) {
offset = {
x: 0,
y: 0
};
}
if (Node === document.body) return offset;
offset.x = offset.x + Node.offsetLeft;
offset.y = offset.y + Node.offsetTop;
return getOffsetSize(Node.offsetParent, offset);
};
注意:這里不可以使用 parentNode 上文已經(jīng)講過 offsetLeft/top 針對的是 HTMLElement.offsetParent 的偏移量而非 parentNode 的偏移量。
Element.getBoundingClientRect
用法講解
Element.getBoundingClientRect() 方法返回元素的大小及其相對于視口的位置。
element.getBoundingClientRect()返回的相對于視口左上角的位置。
element.getBoundingClientRect()返回的 height 和 width 是針對元素可見區(qū)域的寬和高(具體尺寸根據(jù) box-sizing 決定),并不包含滾動條被隱藏的內(nèi)容。
TIP: 如果是標(biāo)準(zhǔn)盒子模型,元素的尺寸等于 width/height + padding + border-width 的總和。如果 box-sizing: border-box,元素的的尺寸等于 width/height。
rectObject = object.getBoundingClientRect();
返回值是一個 DOMRect 對象,這個對象是由該元素的 getClientRects() 方法返回的一組矩形的集合,就是該元素的 CSS 邊框大小。返回的結(jié)果是包含完整元素的最小矩形,并且擁有 left, top, right, bottom, x, y, width, 和 height 這幾個以像素為單位的只讀屬性用于描述整個邊框。除了 width 和 height 以外的屬性是相對于視圖窗口的左上角來計算的。
width和height是計算元素的大小,其他屬性都是相對于視口左上角來說的。
當(dāng)計算邊界矩形時,會考慮視口區(qū)域(或其他可滾動元素)內(nèi)的滾動操作,也就是說,當(dāng)滾動位置發(fā)生了改變,top 和 left 屬性值就會隨之立即發(fā)生變化(因此,它們的值是相對于視口的,而不是絕對的) 。如果你需要獲得相對于整個網(wǎng)頁左上角定位的屬性值,那么只要給 top、left 屬性值加上當(dāng)前的滾動位置(通過 window.scrollX 和 window.scrollY),這樣就可以獲取與當(dāng)前的滾動位置無關(guān)的值。

計算元素是否出現(xiàn)在視口內(nèi)
利用的還是元素距離視口的位置小于視口的大小。
注意即便變成了負(fù)值,那么也表示元素曾經(jīng)出現(xiàn)過在屏幕中只是現(xiàn)在不顯示了而已。(就比如滑動過)
vue-lazy圖片懶加載庫源碼就是這么判斷的。
isInView (): boolean {
const rect = this.el.getBoundingClientRect()
return rect.top < window.innerHeight && rect.left < window.innerWidth
}
如果rect.top < window.innerHeight表示當(dāng)前元素已經(jīng)已經(jīng)出現(xiàn)在(過)頁面中,left同理。
window.getComputedStyle
用法講解
Window.getComputedStyle()方法返回一個對象,該對象在應(yīng)用活動樣式表并解析這些值可能包含的任何基本計算后報告元素的所有CSS屬性的值。 私有的CSS屬性值可以通過對象提供的API或通過簡單地使用CSS屬性名稱進(jìn)行索引來訪問。
let style = window.getComputedStyle(element, [pseudoElt]);
element
- 用于獲取計算樣式的Element。
pseudoElt 可選
- 指定一個要匹配的偽元素的字符串。必須對普通元素省略(或null)。
返回的style是一個實時的 CSSStyleDeclaration 對象,當(dāng)元素的樣式更改時,它會自動更新本身。
總結(jié)
到此這篇關(guān)于js中位置計算的文章就介紹到這了,更多相關(guān)js中的位置計算內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript中創(chuàng)建原子的方法總結(jié)
這篇文章主要給大家總結(jié)介紹了關(guān)于JavaScript中創(chuàng)建原子的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
layui+ssm實現(xiàn)數(shù)據(jù)表格雙擊編輯更新數(shù)據(jù)功能
在使用layui加載后端數(shù)據(jù)請求時,對數(shù)據(jù)選項框進(jìn)行雙擊即可實現(xiàn)數(shù)據(jù)的輸入編輯更改,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-12-12
簡單的JS控制button顏色隨點擊更改的實現(xiàn)方法
下面小編就為大家?guī)硪黄唵蔚腏S控制button顏色隨點擊更改的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04
javascript獲取鼠標(biāo)點擊元素對象(示例代碼)
本篇文章主要介紹了利用javascript獲取鼠標(biāo)點擊元素對象的示例代碼。需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12
javascript實現(xiàn)十秒鐘后注冊按鈕可點擊的方法
這篇文章主要介紹了javascript實現(xiàn)十秒鐘后注冊按鈕可點擊的方法,涉及javascript時間及樣式操作的相關(guān)技巧,需要的朋友可以參考下2015-05-05
JavaScript Perfection kill 測試及答案
近日,在Perfection kill上看到有關(guān)javascript quiz。并做了一下,最終錯了2個(#2,#9),但是,這2道題,在Ie和ff下的答案是不一樣的?!2010-03-03

