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