1. 程式人生 > 程式設計 >一篇文章讓你徹底搞懂js中的位置計算

一篇文章讓你徹底搞懂js中的位置計算

目錄
  • 引言
  • scroll
    • Element.scroll()
    • Element.scrollHeight/scrollWidth
    • Element.scrollLeft/scrollTop
    • 判斷當前元素是否存在滾動條
    • 判斷使用者是否滾動到底部
  • client
    • MouseEvent.clientX/Y
    • Element.clientHeight/clientWidth
    • Element.clientTop/clientLeft
  • offset
    • MouseEvent.offsetX/offsetY
    • offsetWidth/offsetHeight
    • offsetTop/left
    • 計算元素距離 body 的偏移量
  • Element.getBoundingClientRect
    • 用法講解
    • 計算元素是否出現在視口內
  • window.getComputedStyle
    • 用法講解
  • 總結

    引言

    文章中涉及到的api列表:

    1. scroll相關Api
    2. client相關Api
    3. offset相關Api
    4. Element.getBoundingClientRectAPi
    5. 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是指在元素左上方區域橫軸方向上想要顯示的畫素。
    • y-coord是指在元素左上方區域縱軸方向上想要顯示的畫素。

    也就是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的。
    但是如果出現了滾動條的話scrollHeight指的是包含元素不可以見內容的高度,出現滾動條的情況下是scrollHeight恆大於clientHeight。

    • Element.scrollWidth 這也是一個元素內容寬度的只讀屬性,包含由於溢位導致檢視中不可以見的內容。

    http://www.cppcns.com理上和scrollHeight是同理的,只不過這裡是寬度而非高度。

    簡單來說一個元素如果不存在滾動條,那麼他們的scroll和client都是相等的值。如果存在了滾動條,client只會計算出當前元素展示出來的高度/寬度,而scroll不僅僅會計算當前元素展示出的,還會包含當前元素的滾動條隱藏內容的高度/寬度。

    clientWidth/height + [滾動條被隱藏內容寬度/高度] = scrollWidth/Height

    Element.scrollLeft/scrollTop

    • Element.scrollTop屬性可以獲取或設定一個元素的內容垂直滾動的畫素數.
    • Element.scrollLeft屬性可以讀取或設定元素滾動條到元素左邊的距離.

    需要額外注意的是: 注意如果這個元素的內容排列方向(direction) 是rtl(right-to-left) ,那麼滾動條會位於最右側(內容開始處),並且scrollLeft值為0。此時,當你從右到左拖動滾動條時,scrollLeft會從0變為負數。

    scrollLeft/Top在日常工作中是比較頻繁使用關於操作滾動條的相關api,他們是一個可以設定的值。根據不同的值對應可以控制滾動條的位置。

    其實這兩個屬性和上方的Element.scroll()可以達到相同的效果。

    在實際工作中如果對於滾動操作有很頻繁的需求,個人建議去使用better-scroll,它是一個移動/web端的通用滾動庫,內部是基於元素transform去操作的滾動並不會觸發相關重塑/迴流。

    判斷當前元素是否存在滾動條

    出現滾動條便意味著元素空間將大於其內容顯示區域,根據這個現象便可以得到判斷是否出現滾動條的規則。

    export const hasScrolled = (element,direction) => {
      if (!element || element.nodeType !== 1) return;
      if (direcwww.cppcns.comtion === "vertical") {
        return element.scrollHeight > element.clientHeight;
      } else if (direction === "horizontal") {
        return element.scrollWidth > element.clientWidth;
      }
    };
    

    判斷使用者是否滾動到底部

    本質上就是當元素出現滾動條時,判斷當前元素出現的高度 + 滾動條高度 = 元素本身的高度(包含隱藏部分)。

    element.scrollHeight - element.scrollTop === element.clientHeight
    

    client

    MouseEvent.clientX/Y

    MounseEvent.clientX/Y同樣也是隻讀屬性,它提供事件發生時的應用客戶端區域的水平座標。

    例如,不論頁面是否有垂直/水平滾動,當你www.cppcns.com點選客戶端區域的左上角時,滑鼠事件的clientX/Y值都將為 0 。

    其實MouseEvent.clientX/Y也就是相對於當前視口(瀏覽器可視區)進行位置計算。

    轉載一張非常直白的圖:

    一篇文章讓你徹底搞懂js中的位置計算

    Element.clientHeight/clientWidth

    Element.clientWidth/clinetHeight屬性表示元素的內部寬度,以畫素計。該屬性包括內邊距 padding,但不包括邊框 border、外邊距 margin 和垂直滾動條(如果有的話)。

    內聯元素以及沒有 樣式的元素的clientWidth屬性值為 0。

    在不出現滾動條時候Element.clientWidth/Height === Element.scrollWidth/Height

    一篇文章讓你徹底搞懂js中的位置計算

    Element.clientTop/clientLeMxofWXhft

    Element.clientLeft表示一個元素的左邊框的寬度,以畫素表示。如果元素的文字方向是從右向左(RTL,right-to-left),並且由於內容溢位導致左邊出現了一個垂直滾動條,則該屬性包括滾動條的寬度。clientLeft不包括左外邊距和左內邊距。clientLeft是隻讀的。

    同樣的Element.clientTop表示元素上邊框的寬度,也是一個只讀屬性。

    這兩個屬性日常使用會比較少,但是也應該瞭解以避免搞混這些看似名稱都類似的屬性。

    offset

    MouseEvent.offsetX/offsetY

    MouseEvent介面的只讀屬性offsetX/Y 規定了事件物件與目標節點的內填充邊(padding edge)在 X/Y 軸方向上的偏移量。

    相信使用過offest的同學對這個屬性深有體會,它是相對於父元素的左邊/上方的偏移量。

    注意是觸發元素也就是 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 節點的左邊界偏移的畫素值。

    注意返回的是相對於 HTMLElement.offsetParent 節點左邊邊界的偏移量。

    何為HTMLElement.offsetParent?

    HTMLElement.offsetParent 是一個只讀屬性,返回一個指向最近的(指包含層級上的最近)包含該元素的定位元素或者最近的 table,td,th,body 元素。當元素的 style.display 設定為 "none" 時,offsetParent 返回 null。offsetParent 很有用,因為 offsetTop 和 offsetLeft 都是相對於其內邊距邊界的。 -- MDN

    講講人話,當前元素的祖先元件節點如果不存在任何 table,th 以及 position 屬性為 relative,absolute 等為定位元素時,offsetLeft/offsetTop 返回的是距離 body 左/上角的偏移量。

    當祖先元素中有定位元素(或者上述標籤元素)時,它就可以被稱為元素的offsetParent。元素的 offsetLeft/offsetTop 的值等於它的左邊框左側/頂邊框頂部到它的 offsetParent 元素左邊框的距離。

    我們來看看這張圖:

    一篇文章讓你徹底搞懂js中的位置計算

    計算元素距離 body 的偏移量

    當我們需要獲得元素距離 body 的距離時,但是又無法確定父元素是否存在定位元素時(大多數時候在元件開發中,並不清楚父節點是否存在定位)。此時需要實現類似 jqery 的 offset()方法:獲得當前元素對於 body 的偏移量。

    • 無法直接使用 offsetLeft/offsetTop 獲取,因為並不確定父元素是否存在定位元素。
    • 使用遞迴解決,累加偏移量 offset,當前 offsetParent 不為 body 時。
    • 繼續遞歸向上超著 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 上文已經講過 offsetLeft/top 針對的是 HTMLElement.offsetParent 的偏移量而非 parentNode 的偏移量。

    Element.getBoundingClientRect

    用法講解

    Element.getBoundingClientRect() 方法返回元素的大小及其相對於視口的位置。

    element.getBoundingClientRect()返回的相對於視口左上角的位置。

    element.getBoundingClientRect()返回的 height 和 width 是針對元素可見區域的寬和高(具體尺寸根據 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 這幾個以畫素為單位的只讀屬性用於MxofWXh描述整個邊框。除了 width 和 height 以外的屬性是相對於檢視視窗的左上角來計算的。

    width和height是計算元素的大小,其他屬性都是相對於視口左上角來說的。

    當計算邊界矩形時,會考慮視口區域(或其他可滾動元素)內的滾動操作,也就是說,當滾動位置發生了改變,top 和 left 屬性值就會隨之立即發生變化(因此,它們的值是相對於視口的,而不是絕對的) 。如果你需要獲得相對於整個左上角定位的屬性值,那麼只要給 top、left 屬性值加上當前的滾動位置(通過 window.scrollX 和 window.scrollY),這樣就可以獲取與當前的滾動位置無關的值。

    一篇文章讓你徹底搞懂js中的位置計算

    計算元素是否出現在視口內

    利用的還是元素距離視口的位置小於視口的大小。

    注意即便變成了負值,那麼也表示元素曾經出現過在螢幕中只是現在不顯示了而已。(就比如滑動過)

    -lazy圖片懶載入庫原始碼就是這麼判斷的。

     isInView (): boolean {
        const rect = this.el.getBoundingClientRect()
        return rect.top < window.innerHeight && rect.left < window.innerWidth
      }
    

    如果rect.top < window.innerHeight表示當前元素已經已經出現在(過)頁面中,left同理。

    window.getComputedStyle

    用法講解

    Window.getComputedStyle()方法返回一個物件,該物件在應用活動樣式表並解析這些值可能包含的任何基本計算後報告元素的所有CSS屬性的值。 私有的CSS屬性值可以通過物件提供的API或通過簡單地使用CSS屬性名稱進行索引來訪問。

    let style = window.getComputedStyle(element,[pseudoElt]);
    

    element

    • 用於獲取計算樣式的Element。

    pseudoElt可選

    • 指定一個要匹配的偽元素的字串。必須對普通元素省略(或null)。

    返回的style是一個實時的CSSStyleDeclaration物件,當元素的樣式更改時,它會自動更新本身。

    總結

    到此這篇關於js中位置計算的文章就介紹到這了,更多相關js中的位置計算內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!