2022 年 6 月 26 日

視窗大小與捲動

我們如何找出瀏覽器視窗的寬度和高度?我們如何取得文件的完整寬度和高度,包括捲動出去的部分?我們如何使用 JavaScript 捲動頁面?

對於這種類型的資訊,我們可以使用根文件元素 document.documentElement,它對應到 <html> 標籤。但還有其他方法和特殊情況需要考慮。

視窗的寬度/高度

若要取得視窗寬度和高度,我們可以使用 document.documentElementclientWidth/clientHeight

例如,這個按鈕顯示你的視窗高度

不是 window.innerWidth/innerHeight

瀏覽器也支援像 window.innerWidth/innerHeight 之類的屬性。它們看起來就像我們想要的,為什麼不改用它們呢?

如果存在捲軸,且它佔用了一些空間,clientWidth/clientHeight 會提供不包含捲軸的寬度/高度(扣除捲軸)。換句話說,它們會傳回文件的可見部分的寬度/高度,也就是內容可用的部分。

window.innerWidth/innerHeight 包含捲軸列。

如果有一個捲軸列,並且佔用了一些空間,那麼這兩行會顯示不同的值

alert( window.innerWidth ); // full window width
alert( document.documentElement.clientWidth ); // window width minus the scrollbar

在大部分情況下,我們需要可用的視窗寬度,以便在捲軸列(如果有)中繪製或定位某些內容,因此我們應該使用 documentElement.clientHeight/clientWidth

DOCTYPE 很重要

請注意:當 HTML 中沒有 <!DOCTYPE HTML> 時,頂層幾何屬性可能會稍微不同。可能會發生奇怪的事情。

在現代 HTML 中,我們應該始終撰寫 DOCTYPE

文件寬度/高度

理論上,由於根文件元素是 document.documentElement,並且包含所有內容,因此我們可以將文件的完整大小測量為 document.documentElement.scrollWidth/scrollHeight

但在該元素上,對於整個頁面,這些屬性無法按預期工作。在 Chrome/Safari/Opera 中,如果沒有捲動,則 documentElement.scrollHeight 甚至可能小於 documentElement.clientHeight!很奇怪,對吧?

為了可靠地獲取完整的文檔高度,我們應該採用這些屬性的最大值

let scrollHeight = Math.max(
  document.body.scrollHeight, document.documentElement.scrollHeight,
  document.body.offsetHeight, document.documentElement.offsetHeight,
  document.body.clientHeight, document.documentElement.clientHeight
);

alert('Full document height, with scrolled out part: ' + scrollHeight);

為什麼這樣?最好不要問。這些不一致性來自於古代,而不是「智慧」邏輯。

獲取當前捲動

DOM 元素在它們的 scrollLeft/scrollTop 屬性中具有它們當前的捲動狀態。

對於文件捲動,document.documentElement.scrollLeft/scrollTop 在大多數瀏覽器中都能正常工作,除了基於 WebKit 的舊瀏覽器,例如 Safari(錯誤 5991),在這些瀏覽器中,我們應該使用 document.body 而不是 document.documentElement

幸運的是,我們根本不必記住這些特殊性,因為捲動可以在特殊屬性 window.pageXOffset/pageYOffset 中使用

alert('Current scroll from the top: ' + window.pageYOffset);
alert('Current scroll from the left: ' + window.pageXOffset);

這些屬性是唯讀的。

也可以作為 window 屬性 scrollXscrollY 使用

由於歷史原因,這兩個屬性都存在,但它們是相同的

  • window.pageXOffsetwindow.scrollX 的別名。
  • window.pageYOffsetwindow.scrollY 的別名。

捲動:scrollTo、scrollBy、scrollIntoView

重要

要使用 JavaScript 捲動頁面,必須完全建立其 DOM。

例如,如果我們嘗試使用 <head> 中的腳本來捲動頁面,它將不起作用。

可以透過變更 scrollTop/scrollLeft 來捲動一般元素。

我們可以使用 document.documentElement.scrollTop/scrollLeft 對頁面執行相同的操作(Safari 除外,Safari 應改用 document.body.scrollTop/Left)。

或者,有一個更簡單的通用解決方案:特殊方法 window.scrollBy(x,y)window.scrollTo(pageX,pageY)

  • scrollBy(x,y) 方法會將頁面捲動到相對於目前位置。例如,scrollBy(0,10) 會將頁面向下捲動 10px

    下面的按鈕示範這個功能

  • scrollTo(pageX,pageY) 方法會將頁面捲動到絕對座標,讓可見部分的左上角座標為 (pageX, pageY),相對於文件的左上角。這就像設定 scrollLeft/scrollTop 一樣。

    若要捲動到最開頭,我們可以使用 scrollTo(0,0)

這些方法對所有瀏覽器都以相同的方式運作。

scrollIntoView

為了完整性,讓我們介紹另一個方法:elem.scrollIntoView(top)

呼叫 elem.scrollIntoView(top) 會捲動頁面以使 elem 可見。它有一個參數

  • 如果 top=true(這是預設值),則頁面將捲動到使 elem 出現在視窗頂端。元素的上緣將與視窗頂端對齊。
  • 如果 top=false,則頁面將捲動到使 elem 出現在底部。元素的下緣將與視窗底部對齊。

下面的按鈕會捲動頁面,將其自身定位在視窗頂端

而這個按鈕會捲動頁面,將其自身定位在底部

禁止捲動

有時我們需要讓文件「無法捲動」。例如,當我們需要用一個需要立即注意的大訊息覆蓋頁面時,我們希望訪客與該訊息互動,而不是與文件互動。

若要讓文件無法捲動,只需設定 document.body.style.overflow = "hidden" 即可。頁面會「凍結」在目前的捲動位置。

試試看

第一個按鈕凍結捲動,而第二個按鈕則解除凍結。

我們可以使用相同的技術來凍結其他元素的捲動,而不仅仅是 `document.body`。

此方法的缺點是捲軸條會消失。如果它佔用了一些空間,那麼現在這個空間是空的,而內容會「跳動」以填滿它。

這看起來有點奇怪,但如果我們在凍結前後比較 `clientWidth`,就可以解決這個問題。如果它增加了(捲軸條消失了),那麼在 `document.body` 中新增 `padding` 來取代捲軸條,以保持內容寬度相同。

摘要

幾何

  • 文件可見部分的寬度/高度(內容區域寬度/高度):`document.documentElement.clientWidth/clientHeight`

  • 整個文件的寬度/高度,包括捲動出的部分

    let scrollHeight = Math.max(
      document.body.scrollHeight, document.documentElement.scrollHeight,
      document.body.offsetHeight, document.documentElement.offsetHeight,
      document.body.clientHeight, document.documentElement.clientHeight
    );

捲動

  • 讀取目前的捲動:`window.pageYOffset/pageXOffset`。

  • 變更目前的捲動

    • window.scrollTo(pageX,pageY) – 絕對座標,
    • window.scrollBy(x,y) – 相對於目前位置捲動,
    • elem.scrollIntoView(top) – 捲動以使 `elem` 可見(與視窗的頂部/底部對齊)。
教學地圖

評論

在評論之前請先閱讀這段話…
  • 如果你有建議要改進的地方 - 請 提交 GitHub 議題 或發起拉取請求,而不是留言。
  • 如果你無法理解文章中的某些內容 - 請詳細說明。
  • 要插入幾行程式碼,請使用 `<code>` 標籤,對於多行程式碼 - 將它們包在 `<pre>` 標籤中,對於超過 10 行的程式碼 - 使用沙盒 (plnkrjsbincodepen…)