時至今日,構建Web應用程序是前端主要工作之一。這些Web應用程序要面對眾多不同的設備終端,也就是說,我們需要讓這些Web應用程序在各種屏幕、分辨率下都應該有一個較好的展示效果。

要將一個Web應用程序適配眾多終端屏幕,需要知道的是有多少可用的空間。如果你接觸過Web響應式設計(Web Responsive Design),就知道,Web瀏覽器客戶端就可以很好的處理。最簡單的方式就是使用CSS的媒體查詢來處理,但也有很多同學通過JavaScript對最終的結果進行一些控制。不過,使用JavaScript來做的話,有許多計算的事情要做。當然,這些計算雖然是JavaScript來處理,但最終還是由瀏覽器本身來完成。
當涉及到使用JavaScript控制元素位置相關的交互內容時,不能僅依賴瀏覽器自動幫助我們做正確的事情。我們需要主動(人肉)地去做一些相關計算。聽起來可怕,事實上並沒有那麼可怕,接下來的內容將告訴我們怎麼通過JavaScript來做這些事情。當你閱讀完這篇文章之後,你會驚嘆的地說,原來就是這麼的簡單。
測量視窗的大小
不管是什麼設備,都可以通過瀏覽器來查看Web頁面。聽起來很簡單,對吧。從技術上講,這並不完全準確。實際上,你可以通過瀏覽器的viewport 查看Web頁面。
Viewport
Viewport指的是網頁的顯示區域,也就是不借助滾動條的情況下,用戶可以看到的部分網頁大小,中文譯為“視窗”(或“視口”)。正常情況下,viewport 和瀏覽器的顯示窗口是一樣大小的。但是,在移動設備上,兩者可能不是一樣大小。
比如,手機瀏覽器的窗口寬度可能是640px ,這時viewport 寬度就是640px ,但是網頁寬度有1200px ,正常情況下,瀏覽器會提供橫向滾動條,讓用戶查看窗口容納不下的560px 。另一種方法則是,將viewport 設成1200px ,也就是說,瀏覽器的顯示寬度還是640px ,但是網頁的顯示區域達到1200px ,整個網頁縮小了,在瀏覽器中可以看清楚全貌。這樣一來,手機瀏覽器就可以看到網頁在桌面瀏覽器上的顯示效果。
簡單點來說,視窗是指你的瀏覽器中實際用來顯示網頁的部分,比如像下圖這樣:

拿Chrome瀏覽器來舉例,viewport 不包括瀏覽器的地址欄、狀態欄或任何其他類型的用戶界面。因為這些東西會佔用空間。最後有一點很重要,viewport 也不包括滾動條佔用的空間。
言外之意,根據瀏覽器開啟的不同設置(比如瀏覽器窗口狀態欄、地址欄等),你的視窗大小將有所不同:

正如前面提到的,viewport 是可縮放的。既然可以縮放,那就具有可縮放的相關規則。在Web頁面中,可以在HTML文件的<head> 中像下面指定viewport 相關的規則:
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
</head>
上面代碼指定,viewport 的縮放規則是,縮放到當前設備的屏幕寬度(device-width ),初始縮放比例(initial-scale )為1 倍,禁止用戶縮放(user-scalable )。
viewport 全部屬性如下:
width : viewport 的寬度
height : viewport 的高度
initial-scale : viewport 的初始縮放比例
maximum-scale : viewport 的最大縮放比例
minimum-scale : viewport 的最小縮放比例
user-scalable : viewport 的是否允許用戶縮放
事實上,在JavaScript中,咱們測量viewport 的大小也很容易。調用document.documentElement 對象的clientWidth 和clientHeight 屬性就可以測量出viewport 的width 和height 。

比如下面這個示例的代碼,讓你調整瀏覽器窗口大小時會更新viewportWidth (視窗寬度)和viewportHeight (視窗高度)兩個變量的值:
let displayViewportSize = (e) => {
let viewportWidth = document.documentElement.clientWidth
let viewportHeight = document.documentElement.clientHeight
console.log(`viewportWidth: ${viewportWidth},viewportHeight: ${viewportHeight}`)
}
window.addEventListener('resize', displayViewportSize, false)
運行上面的代碼,隨著你改變瀏覽器窗口大小時,將會輸出更改後視窗width 和height 的值,如下所示:

從《DOM樹和遍歷DOM》一節中,我們知道document.documentElement 是HTML的根元素,即<html> 元素。clientWidth 和clientHeight 屬性可以用來獲取元素邊框內區域的大小。也就是說,document.documentElement.clientWidth 和document.documentElement.clientHeight 分別獲取的是html 元素邊框內區域的大小。如果你未接觸過這兩個屬性,建議你花點時間閱讀一下《獲取元素位置和尺寸》一文。
如前所述,viewport 的值不包括水平或垂直滾動條。但有時候,視窗大小的計算有可能也會包含滾動條的大小。如果計算的viewport 大小包括了滾動條大小的話,咱們可以使用window.innerWidth 和window.innerHeight 來進行計算。
注意,如果我們想測試整個瀏覽器的大小,那麼可以使用window.outerWidth 和window.outerHeight 兩個屬性。它們返回瀏覽器窗口整個大小,包括瀏覽器的標題欄、狀態欄等等。
測量屏幕分辨率和尺寸
JavaScript中除了測量瀏覽器和視窗大小的屬性之外,還有其他度量大小的屬性。比如測量屏幕分辨率和尺寸的屬性:

如果我們要測量屏幕的分辨率,可以使用下面這兩對屬性:
window.screen.width 和 window.screen.height 指的是顯示器屏幕的寬度和高度,包括工具欄、狀態欄等;
window.screen.availWidth 和 window.screen.availHeight 指的是瀏覽器窗口在屏幕上可佔用的空間(寬度和高度)
window.screen.width 和 window.screen.height 理論上返回的屏幕完整的分辨率:

這兩個屬性沒有考慮到屏幕(比如任務欄)佔用的空間,所以它們不能準確地知道要處理多少像素。如果要知道屏幕實際有多大尺寸,應該使用window.screen.availWidth 和 window.screen.availHeight :

這兩對屬性在JavaScript中可以像下面這樣使用:
let displayScreenResolution = () => {
let fullWidth = window.screen.width
let fullHeight = window.screen.height
let availableWidth = window.screen.availWidth
let availableHeight = window.screen.availHeight
console.log(`fullWidth: ${fullWidth}px; fullHeight: ${fullHeight}px; availableWidth: ${availableWidth}px; availableHeight: ${availableHeight}px`)
}
window.addEventListener('click', displayScreenResolution, false)

測量document的尺寸
接下來,咱們再來看看document 文檔尺寸的大小。

可以通過document.body 的clientWidth 和clientHeight 屬性來獲取文檔的大小。從前面學習的知識中,我們知道document.body 將獲取的是<body> 元素。
如果我們想要獲取文檔document 的width 和height ,我們可以像這樣寫:
let displayDocumentSize = () => {
let docWidth = document.body.clientWidth
let docHeight = document.body.clientHeight
console.log(`Document's Width: ${docWidth}px; Document's Height: ${docHeight}px`)
}

docWidth 和docHeight 變量存儲的是document 的width 和height 。如果你的document (也就是body 元素)用的是百分比設置單位,你將看到你的文檔的大小會根據視窗的大小變更。
比如,body 設置樣式:
body {
width: 50%;
height: 50%;
}
運行displayDocumentSize() 函數之後,縮放瀏覽器,那麼返回的docWidth 和docHeight 也會隨之變化:

理論上,根文檔元素是documentElement ,也就是對應的<html> 元素。而documentElement.clientWidth 和documentElement.clientHeight 分別獲取的是瀏覽器可視區域的width 和height 。它包含了所有的內容,我們可以使用documentElement.scrollWidth 和documentElement.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
);
滾動條尺寸
與滾動scroll 相關的方法主要有window 對象下的scrollX 、scrollY 、scrollTo 和scroll ;Element 對象下的scrollWidth 、scrollHeight 、scrollLeft 和scrollTop 。
有關於滾動條相關的API介紹,可以閱讀JavaScript學習筆記系列中的《視口寬高、位置與滾動高度》一文。
有一點需要注意,大多數瀏覽器中可以使用documentElement.scrollLeft 和documentElement.scrollTop 來獲取文檔滾動條,但Chrome、Safari和Opera存有bug(比如157855、106133)。如此一來,我們應該使用document.body 來替代document.documentElement 。
幸運的是,在JavaScript中有兩個特殊屬性:
window.pageXOffset :是scrollX 的別名,返回文檔/頁面水平方向滾動的像素值
window.pageYOffset :是scrollY 的別名,返回文檔在垂直方向已滾動的像素值
注意,這兩個屬性是只讀屬性,不能修改。
除了上述提到的有關於滾動條的屬性之外,還有window.scrollTo() ,window.scroll() 和window.scrollBy() 等屬性。
window.scrollTo 方法用於將文檔滾動到指定位置。它接受兩個參數,表示滾動後位於窗口左上角的頁面坐標。
window.scrollTo(x-coord, y-coord)
它也可以接受一個配置對象作為參數。
window.scrollTo(options)
配置對象options 有三個屬性:
top :滾動後頁面左上角的垂直坐標,即 y 坐標
left :滾動後頁面左上角的水平坐標,即 x 坐標
behavior :字符串,表示滾動的方式,有三個可能值(smooth 、instant 、auto ),默認值為auto
我們使用的時候可以像下面這樣使用:
window.scrollTo({
top: 1000,
behavior: 'smooth'
});
window.scroll() 方法是window.scrollTo() 方法的別名。
window.scrollBy() 方法用於將網頁滾動指定距離(單位像素)。它接受兩個參數:水平向右滾動的像素,垂直向下滾動的像素。
window.scrollBy(0, window.innerHeight)
上面代碼用於將網頁向下滾動一屏。
如果不是要滾動整個文檔,而是要滾動某個元素,可以使用下面三個屬性和方法。
Element.scrollTop
Element.scrollLeft
Element.scrollIntoView()
其中scrollIntoView() 是HTML5新增的一個功能:元素滾動的API,功能是類似於錨點。
根據 MDN的描述,Element.scrollIntoView() 方法讓當前的元素滾動到瀏覽器窗口的可視區域內。
Element.scrollIntoView() 方法還有一個變體,即:Element.scrollIntoViewIfNeeded() 。該方法也是用來將不在瀏覽器窗口的可見區域內的元素滾動到瀏覽器窗口的可見區域。但如果該元素已經在瀏覽器窗口的可見區域內,則不會發生滾動。
有關於scrollIntoView 與 scrollIntoViewIfNeeded 的 API 介紹,這裡不深入下去。如果感興趣的話,可以閱讀這篇文章。
總結
在《獲取元素位置和尺寸》和《視口寬高、位置與滾動高度》兩篇文章都有涉及JavaScript對視窗、滾動條、文檔、元素大小尺寸相關的屬性。由於自己是JavaScript的初學者,整理的相關學習筆記有點零亂。最後在這裡簡單的擼一下他們之間的關係。
屬性名稱 |
描述 |
備註 |
offsetParent |
返回一個指向最近的(closest,指包含層級上的最近)包含該元素的定位元素 |
可以使用offsetParent獲取最近的CSS位置(CSS-Positioned)的祖先 |
offsetLeft |
當前元素左上角相對於offsetParent 節點的左邊界偏移的像素值 |
|
offsetTop |
當前元素相對於其 offsetParent 元素的頂部的距離 |
|
offsetWidth |
一個元素的佈局寬度 |
測量包含元素的邊框、水平線上的內邊距、豎直方向滾動條以及CSS設置的寬度的值 |
offsetHeight |
元素的像素高度,高度包含該元素的垂直內邊距和邊框,且是一個整數 |
|
clientTop/Left |
返回該方向的border 寬度 |
該屬性不包含元素的padding 或margin |
clientWidth/Height |
獲取元素邊框內區域的大小 |
包括了內容的寬度和padding ,但不包含滾動條寬度 |
scroll |
滾動窗口至文檔中的特定位置 |
window.scrollTo 同樣能高效地完成同樣的任務 |
scrollLeft |
讀取或設置元素滾動條到元素左邊的距離 |
|
scrollTop |
獲取或設置一個元素的內容垂直滾動的像素數 |
|
scrollWidth |
返回該元素區域寬度和自身寬度中較大的一個 |
|
scrollHeight |
返回該元素內容高度 |
包括被overflow 隱藏掉的部分,包含padding ,但不包含margin |
scrollX |
返回文檔/頁面水平方向滾動的像素值 |
pageXOffset 是scrollX 的別名 |
scrollY |
返回文檔在垂直方向已滾動的像素值 |
pageYOffset 是 scrollY 的別名 |
window.innerHeight |
瀏覽器窗口高度,如果存在水平滾動條,則包括滾動條 |
|
window.outerHeight |
瀏覽器窗口整個高度,包括窗口標題、工具欄、狀態欄等 |
|
window.innerWidth |
瀏覽器窗口寬度,如果存在垂直滾動條,則包括滾動條 |
|
window.outerWidth |
瀏覽器窗口整個寬度,包括側邊欄,窗口鑲邊和調正窗口大小的邊框 |
|
|