一篇文章讓你徹底搞懂js中的位置計(jì)算
引言
文章中涉及到的api列表:
- scroll相關(guān)Api
- client相關(guān)Api
- offset相關(guān)Api
- Element.getBoundingClientRectAPi
- Window.getComputedStyleApi
我們會(huì)結(jié)合api定義,知名開源庫(kù)中的應(yīng)用場(chǎng)景來逐層分析這些api。足以應(yīng)對(duì)工作中關(guān)于元素位置計(jì)算的大部分場(chǎng)景。
注意在使用位置計(jì)算api時(shí)要格外的小心,不合理的使用他們可能會(huì)造成布局抖動(dòng)Layout Thrashing影響頁(yè)面渲染。
scroll
首先我們先來看看scroll相關(guān)的屬性和方法。
Element.scroll()
Element.scroll()方法是用于在給定的元素中滾動(dòng)到某個(gè)特定坐標(biāo)的Element 接口。
element.scroll(x-coord, y-coord) element.scroll(options)
- x-coord 是指在元素左上方區(qū)域橫軸方向上想要顯示的像素。
- y-coord 是指在元素左上方區(qū)域縱軸方向上想要顯示的像素。
也就是element.scroll(x,y)會(huì)將元素滾動(dòng)條位置滾動(dòng)到對(duì)應(yīng)x,y的位置。
同時(shí)也支持element.scroll(options)方式調(diào)用,支持傳入額外的配置:
{ left: number, top: number, behavior: 'smooth' | 'auto' // 平滑滾動(dòng)還是默認(rèn)直接滾動(dòng) }
Element.scrollHeight/scrollWidth
Element.scrollHeight 這個(gè)只讀屬性是一個(gè)元素內(nèi)容高度的度量,包括由于溢出導(dǎo)致的視圖中不可見內(nèi)容。
scrollHeight 的值等于該元素在不使用滾動(dòng)條的情況下為了適應(yīng)視口中所用內(nèi)容所需的最小高度。 沒有垂直滾動(dòng)條的情況下,scrollHeight值與元素視圖填充所有內(nèi)容所需要的最小值clientHeight相同。包括元素的padding,但不包括元素的border和margin。scrollHeight也包括 ::before 和 ::after這樣的偽元素。
換句話說Element.scrollHeight在元素不存在滾動(dòng)條的情況下是恒等于clientHeight的。
但是如果出現(xiàn)了滾動(dòng)條的話scrollHeight指的是包含元素不可以見內(nèi)容的高度,出現(xiàn)滾動(dòng)條的情況下是scrollHeight恒大于clientHeight。
- Element.scrollWidth 這也是一個(gè)元素內(nèi)容寬度的只讀屬性,包含由于溢出導(dǎo)致視圖中不可以見的內(nèi)容。
原理上和scrollHeight是同理的,只不過這里是寬度而非高度。
簡(jiǎn)單來說一個(gè)元素如果不存在滾動(dòng)條,那么他們的scroll和client都是相等的值。如果存在了滾動(dòng)條,client只會(huì)計(jì)算出當(dāng)前元素展示出來的高度/寬度,而scroll不僅僅會(huì)計(jì)算當(dāng)前元素展示出的,還會(huì)包含當(dāng)前元素的滾動(dòng)條隱藏內(nèi)容的高度/寬度。
clientWidth/height + [滾動(dòng)條被隱藏內(nèi)容寬度/高度] = scrollWidth/Height
Element.scrollLeft/scrollTop
- Element.scrollTop 屬性可以獲取或設(shè)置一個(gè)元素的內(nèi)容垂直滾動(dòng)的像素?cái)?shù).
- Element.scrollLeft 屬性可以讀取或設(shè)置元素滾動(dòng)條到元素左邊的距離.
需要額外注意的是: 注意如果這個(gè)元素的內(nèi)容排列方向(direction) 是rtl (right-to-left) ,那么滾動(dòng)條會(huì)位于最右側(cè)(內(nèi)容開始處),并且scrollLeft值為0。此時(shí),當(dāng)你從右到左拖動(dòng)滾動(dòng)條時(shí),scrollLeft會(huì)從0變?yōu)樨?fù)數(shù)。
scrollLeft/Top在日常工作中是比較頻繁使用關(guān)于操作滾動(dòng)條的相關(guān)api,他們是一個(gè)可以設(shè)置的值。根據(jù)不同的值對(duì)應(yīng)可以控制滾動(dòng)條的位置。
其實(shí)這兩個(gè)屬性和上方的Element.scroll()可以達(dá)到相同的效果。
在實(shí)際工作中如果對(duì)于滾動(dòng)操作有很頻繁的需求,個(gè)人建議去使用better-scroll,它是一個(gè)移動(dòng)/web端的通用js滾動(dòng)庫(kù),內(nèi)部是基于元素transform去操作的滾動(dòng)并不會(huì)觸發(fā)相關(guān)重塑/回流。
判斷當(dāng)前元素是否存在滾動(dòng)條
出現(xiàn)滾動(dòng)條便意味著元素空間將大于其內(nèi)容顯示區(qū)域,根據(jù)這個(gè)現(xiàn)象便可以得到判斷是否出現(xiàn)滾動(dòng)條的規(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; } };
判斷用戶是否滾動(dòng)到底部
本質(zhì)上就是當(dāng)元素出現(xiàn)滾動(dòng)條時(shí),判斷當(dāng)前元素出現(xiàn)的高度 + 滾動(dòng)條高度 = 元素本身的高度(包含隱藏部分)。
element.scrollHeight - element.scrollTop === element.clientHeight
client
MouseEvent.clientX/Y
MounseEvent.clientX/Y同樣也是只讀屬性,它提供事件發(fā)生時(shí)的應(yīng)用客戶端區(qū)域的水平坐標(biāo)。
例如,不論頁(yè)面是否有垂直/水平滾動(dòng),當(dāng)你點(diǎn)擊客戶端區(qū)域的左上角時(shí),鼠標(biāo)事件的 clientX/Y 值都將為 0 。
其實(shí)MouseEvent.clientX/Y也就是相對(duì)于當(dāng)前視口(瀏覽器可視區(qū))進(jìn)行位置計(jì)算。
轉(zhuǎn)載一張非常直白的圖:
Element.clientHeight/clientWidth
Element.clientWidth/clinetHeight 屬性表示元素的內(nèi)部寬度,以像素計(jì)。該屬性包括內(nèi)邊距 padding,但不包括邊框 border、外邊距 margin 和垂直滾動(dòng)條(如果有的話)。
內(nèi)聯(lián)元素以及沒有 CSS 樣式的元素的 clientWidth 屬性值為 0。
在不出現(xiàn)滾動(dòng)條時(shí)候Element.clientWidth/Height === Element.scrollWidth/Height
Element.clientTop/clientLeft
Element.clientLeft表示一個(gè)元素的左邊框的寬度,以像素表示。如果元素的文本方向是從右向左(RTL, right-to-left),并且由于內(nèi)容溢出導(dǎo)致左邊出現(xiàn)了一個(gè)垂直滾動(dòng)條,則該屬性包括滾動(dòng)條的寬度。clientLeft 不包括左外邊距和左內(nèi)邊距。clientLeft 是只讀的。
同樣的Element.clientTop表示元素上邊框的寬度,也是一個(gè)只讀屬性。
這兩個(gè)屬性日常使用會(huì)比較少,但是也應(yīng)該了解以避免搞混這些看似名稱都類似的屬性。
offset
MouseEvent.offsetX/offsetY
MouseEvent 接口的只讀屬性 offsetX/Y 規(guī)定了事件對(duì)象與目標(biāo)節(jié)點(diǎn)的內(nèi)填充邊(padding edge)在 X/Y 軸方向上的偏移量。
相信使用過offest的同學(xué)對(duì)這個(gè)屬性深有體會(huì),它是相對(duì)于父元素的左邊/上方的偏移量。
注意是觸發(fā)元素也就是 e.target,額外小心如果事件對(duì)象中存在從一個(gè)子元素當(dāng)移動(dòng)到子元素內(nèi)部時(shí),e.offsetX/Y 此時(shí)相對(duì)于子元素的左上角偏移量。
offsetWidth/offsetHeight
HTMLElement.offsetWidth/Height 是一個(gè)只讀屬性,返回一個(gè)元素的布局寬度/高度。
所謂的布局寬度也就是相對(duì)于我們上邊說到的clientHeight/Width,offsetHeight/Width,他們都是不包含border以及滾動(dòng)條的寬/高(如果存在的話)。
而offsetWidth/offsetHeight返回元素的布局寬度/高度,包含元素的邊框(border)、水平線/垂直線上的內(nèi)邊距(padding)、豎直/水平方向滾動(dòng)條(scrollbar)(如果存在的話)、以及CSS設(shè)置的寬度(width)的值。
offsetTop/left
HTMLElement.offsetLeft 是一個(gè)只讀屬性,返回當(dāng)前元素左上角相對(duì)于 HTMLElement.offsetParent 節(jié)點(diǎn)的左邊界偏移的像素值。
注意返回的是相對(duì)于 HTMLElement.offsetParent 節(jié)點(diǎn)左邊邊界的偏移量。
何為HTMLElement.offsetParent?
HTMLElement.offsetParent 是一個(gè)只讀屬性,返回一個(gè)指向最近的(指包含層級(jí)上的最近)包含該元素的定位元素或者最近的 table,td,th,body 元素。當(dāng)元素的 style.display 設(shè)置為 "none" 時(shí),offsetParent 返回 null。offsetParent 很有用,因?yàn)?offsetTop 和 offsetLeft 都是相對(duì)于其內(nèi)邊距邊界的。 -- MDN
講講人話,當(dāng)前元素的祖先組件節(jié)點(diǎn)如果不存在任何 table,td,th 以及 position 屬性為 relative,absolute 等為定位元素時(shí),offsetLeft/offsetTop 返回的是距離 body 左/上角的偏移量。
當(dāng)祖先元素中有定位元素(或者上述標(biāo)簽元素)時(shí),它就可以被稱為元素的offsetParent。元素的 offsetLeft/offsetTop 的值等于它的左邊框左側(cè)/頂邊框頂部到它的 offsetParent 元素左邊框的距離。
我們來看看這張圖:
計(jì)算元素距離 body 的偏移量
當(dāng)我們需要獲得元素距離 body 的距離時(shí),但是又無(wú)法確定父元素是否存在定位元素時(shí)(大多數(shù)時(shí)候在組件開發(fā)中,并不清楚父節(jié)點(diǎn)是否存在定位)。此時(shí)需要實(shí)現(xiàn)類似 jqery 的 offset()方法:獲得當(dāng)前元素對(duì)于 body 的偏移量。
- 無(wú)法直接使用 offsetLeft/offsetTop 獲取,因?yàn)椴⒉淮_定父元素是否存在定位元素。
- 使用遞歸解決,累加偏移量 offset,當(dāng)前 offsetParent 不為 body 時(shí)。
- 繼續(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 針對(duì)的是 HTMLElement.offsetParent 的偏移量而非 parentNode 的偏移量。
Element.getBoundingClientRect
用法講解
Element.getBoundingClientRect() 方法返回元素的大小及其相對(duì)于視口的位置。
element.getBoundingClientRect()返回的相對(duì)于視口左上角的位置。
element.getBoundingClientRect()返回的 height 和 width 是針對(duì)元素可見區(qū)域的寬和高(具體尺寸根據(jù) box-sizing 決定),并不包含滾動(dòng)條被隱藏的內(nèi)容。
TIP: 如果是標(biāo)準(zhǔn)盒子模型,元素的尺寸等于 width/height + padding + border-width 的總和。如果 box-sizing: border-box,元素的的尺寸等于 width/height。
rectObject = object.getBoundingClientRect();
返回值是一個(gè) DOMRect 對(duì)象,這個(gè)對(duì)象是由該元素的 getClientRects() 方法返回的一組矩形的集合,就是該元素的 CSS 邊框大小。返回的結(jié)果是包含完整元素的最小矩形,并且擁有 left, top, right, bottom, x, y, width, 和 height 這幾個(gè)以像素為單位的只讀屬性用于描述整個(gè)邊框。除了 width 和 height 以外的屬性是相對(duì)于視圖窗口的左上角來計(jì)算的。
width和height是計(jì)算元素的大小,其他屬性都是相對(duì)于視口左上角來說的。
當(dāng)計(jì)算邊界矩形時(shí),會(huì)考慮視口區(qū)域(或其他可滾動(dòng)元素)內(nèi)的滾動(dòng)操作,也就是說,當(dāng)滾動(dòng)位置發(fā)生了改變,top 和 left 屬性值就會(huì)隨之立即發(fā)生變化(因此,它們的值是相對(duì)于視口的,而不是絕對(duì)的) 。如果你需要獲得相對(duì)于整個(gè)網(wǎng)頁(yè)左上角定位的屬性值,那么只要給 top、left 屬性值加上當(dāng)前的滾動(dòng)位置(通過 window.scrollX 和 window.scrollY),這樣就可以獲取與當(dāng)前的滾動(dòng)位置無(wú)關(guān)的值。
計(jì)算元素是否出現(xiàn)在視口內(nèi)
利用的還是元素距離視口的位置小于視口的大小。
注意即便變成了負(fù)值,那么也表示元素曾經(jīng)出現(xiàn)過在屏幕中只是現(xiàn)在不顯示了而已。(就比如滑動(dòng)過)
vue-lazy圖片懶加載庫(kù)源碼就是這么判斷的。
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)在(過)頁(yè)面中,left同理。
window.getComputedStyle
用法講解
Window.getComputedStyle()方法返回一個(gè)對(duì)象,該對(duì)象在應(yīng)用活動(dòng)樣式表并解析這些值可能包含的任何基本計(jì)算后報(bào)告元素的所有CSS屬性的值。 私有的CSS屬性值可以通過對(duì)象提供的API或通過簡(jiǎn)單地使用CSS屬性名稱進(jìn)行索引來訪問。
let style = window.getComputedStyle(element, [pseudoElt]);
element
- 用于獲取計(jì)算樣式的Element。
pseudoElt 可選
- 指定一個(gè)要匹配的偽元素的字符串。必須對(duì)普通元素省略(或null)。
返回的style是一個(gè)實(shí)時(shí)的 CSSStyleDeclaration 對(duì)象,當(dāng)元素的樣式更改時(shí),它會(huì)自動(dòng)更新本身。
總結(jié)
到此這篇關(guān)于js中位置計(jì)算的文章就介紹到這了,更多相關(guān)js中的位置計(jì)算內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- js與jquery中獲取當(dāng)前鼠標(biāo)的x、y坐標(biāo)位置的代碼
- 使用JS獲取當(dāng)前地理位置方法匯總
- js獲取元素在瀏覽器中的絕對(duì)位置
- js實(shí)現(xiàn)滾動(dòng)條滾動(dòng)到某個(gè)位置便自動(dòng)定位某個(gè)tr
- js獲取元素相對(duì)窗口位置的實(shí)現(xiàn)代碼
- JS中獲取 DOM 元素的絕對(duì)位置實(shí)例詳解
- JS控制彈出新頁(yè)面窗口位置和大小的方法
- js獲取鼠標(biāo)位置實(shí)例詳解
- JS獲取當(dāng)前地理位置的方法
- js實(shí)現(xiàn)獲取鼠標(biāo)當(dāng)前的位置
相關(guān)文章
html+javascript+bootstrap實(shí)現(xiàn)層級(jí)多選框全層全選和多選功能
想做一個(gè)先按層級(jí)排序并可以多選的功能,首先傾向于用多層標(biāo)簽式的,直接選定加在文本域里,接下來通過本文給大家介紹html+javascript+bootstrap實(shí)現(xiàn)層級(jí)多選框全層全選和多選功能,需要的朋友參考下2017-03-03微信小程序如何同時(shí)獲取用戶信息和用戶手機(jī)號(hào)
小程序登錄是現(xiàn)在小程序里面很普遍的一個(gè)功能,因?yàn)楣俜教峁┑姆椒?可以一鍵獲取到用戶信息,一鍵拿到手機(jī)號(hào),這篇文章主要給大家介紹了關(guān)于微信小程序如何同時(shí)獲取用戶信息和用戶手機(jī)號(hào)的相關(guān)資料,需要的朋友可以參考下2021-08-08原生javascript實(shí)現(xiàn)無(wú)間縫滾動(dòng)示例
原生javascript無(wú)間縫滾動(dòng)目前支持的是豎向與橫向滾動(dòng),下面有個(gè)不錯(cuò)的示例,大家可以參考下2014-01-01一文總結(jié)JS中邏輯運(yùn)算符的特點(diǎn)
在JavaScript的眾多運(yùn)算符里,提供了三個(gè)邏輯運(yùn)算符&&、||和!,下面這篇文章主要給大家介紹了關(guān)于JS中邏輯運(yùn)算符的特點(diǎn),文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03javascript圖片延遲加載實(shí)現(xiàn)方法及思路
這篇文章主要介紹了javascript圖片延遲加載實(shí)現(xiàn)方法及思路,有時(shí)我們需要用懶加載,也就是延遲加載圖片的方式,來提高網(wǎng)站的親和力,需要的朋友可以參考下2015-12-12利用uniapp+vue3+js適配微信隱私協(xié)議開發(fā)指南
這篇文章主要給大家介紹了關(guān)于利用uniapp+vue3+js適配微信隱私協(xié)議開發(fā)指南的相關(guān)資料,適配最新微信小程序隱私協(xié)議開發(fā)指南,兼容uniapp版本,需要的朋友可以參考下2023-12-12