[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 @ 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
- Element size and scrolling @ JavaScript.info
- Layout and the containing block @ MDN
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、或者使用的transform
、backdrop-filter
等會造成 containing box 的情況。
對於 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/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 @ JavaScript.info
scroll
scrollHeight
和 scrollWidth
指的是內容的實際高度或寬度,也就是包含了使用者需要透過捲動捲軸才能看到的內容。
圖片來源:scrollWidth/scrollHeight。
scrollTop
和 scrollLeft
則是指使用者因為捲動捲軸後而看不到的內容寬高。換句話說,就是「使用者已經捲動了多少內容」:
圖片來源:scrollLeft/scrollTop @ JavaScript.info
建議不要使用 getComputedStyle 取得元素的寬高
getComputedStyle
是透過 CSS 取得 element 的尺寸和位置,因此在回傳的值中會包含 CSS 的單位,例如 px
。
在 Don’t take width/height from CSS @ JavaScript.info 中提到建議不要使用 getComputedStyle 的方式來取得元素的寬高,而是建議使用上面提到的這些 geometry properties,原因包含:
- CSS 元素的寬高取決於 box-size,一旦 box-size 改變,可能會導致 JavaScript 壞掉(?)
- CSS 元素的 width/height 可能會拿到
auto
getComputedStyle
取得的值在不同瀏覽器可能有不同的實作- 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
- Window sizes and scrolling @ javascript.info
- Window @ MDN
如果要取得 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 的寬度
取得整份 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),本質上是一樣的。
scrollY
、pageYOffset
是 window 的屬性;scrollTop
、scrollHeight
則是 DOMElement 的屬性。
- window.scrollY @ MDN
- element.scrollTop @ MDN
- element.scrollLeft @ MDN
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,
};
width
和 height
會包含該元素的 padding
和 border-width
;但如果有設定 box-sizing: border-box
的話,這就會直接是該元素的 width 和 height。
x / left
、y / top
會以元件的左上角作為定位點,bottom
和 right
這是以元件的右下角作為定位點。
mouseEvents
offset
offsetX
, offsetY
是指相對於觸發事件元素的左上角的偏移。但在不同的瀏覽器中其值又有所不同。在 chrome opera, safari 中是指外邊緣,即把該元素邊框的寬度計算在內,firefox ie 則不包含邊框值。如下圖(一個具有藍色邊框的 div)所示:
layer
layerX, layerY 是相對於 position 為 absolute 或 relative 的父元素外邊緣的距離。
最外層是一個綠色邊框的 div,裡面一層是一個紅色邊框的 div, 最裡面是一個藍色邊框的 div, 在藍色 div 上單擊,則該事件的 layerX 與 layerY 相相對於最外層綠色外邊緣的距離(中間紅色的 div 的 position 設有被設置為 absolute 或 relative, 所有不是相對該元素,反之則是相對於該元素):
pageX, pageY, x, y, clientX, clientY
pageX
, pageY
是相對於文檔窗口的左上角,可以想成是相對於 <HTML>
的位置,又稱 layout viewport
。
x
,y
和 clientX
, clientY
相同,其值是相對於可視窗口(瀏覽的可視區域)的左上角(viewport),又稱 visual viewport
。
screenX
, screenY
則會根據裝置解析度而變,通常用不到。
示範程式碼與畫面 @ JSBin
此時可視窗口與文檔窗口重疊,pageX 等於 clientX, pageY 等於 clientY, 如果我們縮小瀏覽器窗口直到瀏覽器出現滾動條。此時可視窗口左上角位置不變,但文檔窗口左上角位置發生的變化,如下圖:
由此我們可以看出當瀏覽器沒有滾動條時(可視窗口與文檔窗口重合),pageX 與 clientX 相等,pageX 與 clientY 相等,如果出現下拉滾動條並向下拉動滾動條,文檔窗口向上滾動,如果出現左右滑動的滾動條並向右拉動滾動條,文檔窗口向左滾動,在文檔窗口滾動的情況下,pageX >= clientX, pageY >= clientY, x = clientX, y = clientY。
資料來源
- 👍 Element size and scrolling @ javascript.info
- javascript 中 x offsetX clientX screenX pageX 的區別 @ 壹讀
- What is the difference between screenX/Y, clientX/Y and pageX/Y? @ StackOverflow
- 圖解 offsetLeft、offsetTop、offsetWidth 和 offsetHeight @ Pixnet