跳至主要内容

[note] Browser Coordinate System │ 瀏覽器座標系統

keywords: offset, layer, page, client, coordinate system, 位置

TL;DR

先想想:

  • 這個數值是屬於相對某個點的距離、位置(例如,x, y, left, top, right, bottom);或是屬於大小尺寸(width、height)
  • 這個數值包不包含 scrollbar
  • 這個數值包不包含 browser 的工具列或網址列在內

Element size and scrolling

圖片來源:Element size and scrolling @ javascript.info

  • clientWidth/clientHeight 和 offsetWidth/offsetHeight 最大的差別在於「後者」除了同樣有 content 和 padding 之外,還多了 border、scrollbar 的寬高
  • scrollWidth/scrollHeight 是實際上內容的完成高度或寬度,包含了使用者需要捲動捲軸才能看到的部分。
  • offsetLeft/offsetTop:指游標到"容器"(offsetParent)左上角的偏移量(offsetParent.id)。
  • client:指游標到 browser window 左上角的偏移量(不管 HTML document),滾動瀏覽器捲軸時,同樣位置座標相同。又稱 visual viewport
  • page:指游標到 document 左上角的偏移量,滾動瀏覽器捲軸時,同樣位置座標不同,又稱作 layout viewport

示範程式碼與畫面 @ CodePen

HTMLElement

offset(和 offsetParent 的相對距離)

  • 具有 containing box 的 element 可以是其他元素的 offsetParent。
  • offsetLeft, offsetTop 是 Element 的距離(位置)屬性,指該 element 到 offsetParent 的距離。
  • offsetWidth, offsetHeight 是 Element 的尺寸(大小)屬性,指該 element 的寬高(包含了元素本身的 padding、border 和 scrollbar)。
  • 在使用 offset 前最好確定 offsetParent 是誰,一般的情況下,offsetParent 會是 <body> 除非它的 outerContainer 有設定 position、table、或者使用的 transformbackdrop-filter 等會造成 containing box 的情況。
warning

對於 position:fixed 的元素來說,它的 offset parent 會是 null

/**
* offsetLeft, offsetTop 是 Element 的屬性,指該 element 到 offsetParent 的距離。
* offsetWidth, offsetHeight 是 Element 的屬性,指該 element 的寬高。
*
* 在使用 offset 前最好確定 offsetParent 是誰
* 一般的情況下,offsetParent 會是 <body> 除非它的 outerContainer 有設定 position
**/
element.offsetParent;

element.offsetTop;
element.offsetLeft; // 表示 Element 左邊距離與 offsetParent 左邊界距離

element.offsetHeight;
element.offsetWidth;

offsetWidth/Height

圖片來自:offsetWidth/Hight @ JavaScript.info

要留意這裡的 offsetTop, offsetLeft 的效果和 mouseEvent 中的 offsetX, offsetY 有些不同。offsetX, offsetY 是 Event 的屬性,指滑鼠到外層容器的距離:

/**
* offsetX, offsetY 是 Event 的屬性,指滑鼠到外層容器的距離。
**/
event.offsetX;
event.offsetY;

client

  • clientTop/clientLeft 通常等同於 borderTop 和 borderLeft,除非 scrollbar 是在左邊則需要加入 scrollbar 的寬度。
  • clientWidth/clientHeight 不包含 border 和 scrollbar 的長寬(但包含 padding)。
提示

clientWidth/clientHeight 和 offsetWidth/offsetHeight 最大的差別在於後者除了同樣有 padding 之外,還多了 border、scrollbar 的寬高

clientWidth/Height

圖片來源:clientWidth/Height @ JavaScript.info

scroll

scrollHeightscrollWidth 指的是內容的實際高度或寬度,也就是包含了使用者需要透過捲動捲軸才能看到的內容。

scrollHeight/scrollHeight

圖片來源:scrollWidth/scrollHeight

scrollTopscrollLeft 則是指使用者因為捲動捲軸後而看不到的內容寬高。換句話說,就是「使用者已經捲動了多少內容」

Screen Shot 2022-02-21 at 1.49.26 PM

圖片來源:scrollLeft/scrollTop @ JavaScript.info

建議不要使用 getComputedStyle 取得元素的寬高

getComputedStyle 是透過 CSS 取得 element 的尺寸和位置,因此在回傳的值中會包含 CSS 的單位,例如 px

Don’t take width/height from CSS @ JavaScript.info 中提到建議不要使用 getComputedStyle 的方式來取得元素的寬高,而是建議使用上面提到的這些 geometry properties,原因包含:

  1. CSS 元素的寬高取決於 box-size,一旦 box-size 改變,可能會導致 JavaScript 壞掉(?)
  2. CSS 元素的 width/height 可能會拿到 auto
  3. getComputedStyle 取得的值在不同瀏覽器可能有不同的實作
  4. geometry properties 取得的是 number,但 getComputedStyle(elem).width 取得的是帶有 px 的字串

The difference: CSS width versus clientWidth @ JavaScript Info

const target = document.getElementById('target-element');
target.offsetTop; // 0(相對於 offsetParent 的距離)
window.getComputedStyled(target).top; // '0px'(實際 CSS 的屬性值)

怎麼知道使用者滾到底部了?

let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight;

Window

如果要取得 root document 元素(通常都是 <html>),可以使用 document.documentElement

取得瀏覽器視窗大小

window.outerHeight; // 整個「瀏覽器」的高度,包含瀏覽器導覽列等等在內
window.innerHeight; // 整個 window 的高度(包含 scrollbar),但不包含瀏覽器導覽列等等的高度
document.documentElement.clientHeight; // window 的高度扣掉 scrollbar 的高度

window.outerWidth; // 整個「瀏覽器」的寬度,包含瀏覽器導覽列等等在內
window.innerWidth; // 整個 window 的寬度(包含 scrollbar)
document.documentElement.clientWidth; // window 的寬度扣掉 scrollbar 的寬度

innerHeight vs outerHeight illustration

取得整份 document 的大小(寬高)

document 指的是整份網頁(超過 viewport),由於各瀏覽器的實作方式不同,如果我們想要取的整份 document 的尺寸,比較好的方式是:

// https://javascript.info/size-and-scroll-window

// 取得整份 document 的高度
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);

取得瀏覽器捲軸的位置

// 指瀏覽器捲軸目前距離最上方的距離
// 如果捲軸位於網頁最上方(完全沒往下滑,則 scrollY = 0)

window.scrollY;
window.pageYOffset;
window.pageYOffset == window.scrollY; // always true

// 邏輯上是一樣的
document.documentElement.scrollTop;

// 如果要考慮跨瀏覽器兼容性(cross-browser capability)的話,建議使用下面的寫法:
const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop;
提示

window.pageYOffset 只是 window.scrollY 的另一個名稱(alias),本質上是一樣的。

warning

scrollYpageYOffset 是 window 的屬性;scrollTopscrollHeight 則是 DOMElement 的屬性。

element.getBoundingClientRect():元素相對於 viewport 的尺寸和位置

getBoundingClientRect @ MDN

回傳的是 DOMRect 物件,它會包含了 padding 和 border-width 後得到的 left, top, right, bottom, x, y ,width , height,其中除了 width 和 height 之外,其他的座標點都是相對於 visual viewport 的左上角。

const rectObject = obj.getBoundingClientRect();

rectObject = {
/** 位置相關的數值 */
x: 26, // left
left: 26, // x
y: 26, // top
top: 26, // y
right: 76,
bottom: 76,

/** 尺寸(大小)相關的數值 */
width: 50,
height: 50,
};
提示

widthheight 會包含該元素的 paddingborder-width;但如果有設定 box-sizing: border-box 的話,這就會直接是該元素的 width 和 height。

提示

x / lefty / top 會以元件的左上角作為定位點,bottomright 這是以元件的右下角作為定位點。

img

mouseEvents

offset

offsetX, offsetY 是指相對於觸發事件元素的左上角的偏移。但在不同的瀏覽器中其值又有所不同。在 chrome opera, safari 中是指外邊緣,即把該元素邊框的寬度計算在內,firefox ie 則不包含邊框值。如下圖(一個具有藍色邊框的 div)所示:

img

layer

layerX, layerY 是相對於 position 為 absolute 或 relative 的父元素外邊緣的距離。

最外層是一個綠色邊框的 div,裡面一層是一個紅色邊框的 div, 最裡面是一個藍色邊框的 div, 在藍色 div 上單擊,則該事件的 layerX 與 layerY 相相對於最外層綠色外邊緣的距離(中間紅色的 div 的 position 設有被設置為 absolute 或 relative, 所有不是相對該元素,反之則是相對於該元素):

img

pageX, pageY, x, y, clientX, clientY

pageX, pageY 是相對於文檔窗口的左上角,可以想成是相對於 <HTML> 的位置,又稱 layout viewport

x,yclientX, clientY 相同,其值是相對於可視窗口(瀏覽的可視區域)的左上角(viewport),又稱 visual viewport

screenX, screenY 則會根據裝置解析度而變,通常用不到。

示範程式碼與畫面 @ JSBin

img

img

此時可視窗口與文檔窗口重疊,pageX 等於 clientX, pageY 等於 clientY, 如果我們縮小瀏覽器窗口直到瀏覽器出現滾動條。此時可視窗口左上角位置不變,但文檔窗口左上角位置發生的變化,如下圖:

img

由此我們可以看出當瀏覽器沒有滾動條時(可視窗口與文檔窗口重合),pageX 與 clientX 相等,pageX 與 clientY 相等,如果出現下拉滾動條並向下拉動滾動條,文檔窗口向上滾動,如果出現左右滑動的滾動條並向右拉動滾動條,文檔窗口向左滾動,在文檔窗口滾動的情況下,pageX >= clientX, pageY >= clientY, x = clientX, y = clientY。

資料來源