響應式設計——initial containing block、viewport以及相關尺寸
在閱讀這篇文章之前,你需要了解裝置畫素、邏輯畫素(裝置獨立畫素)和CSS畫素的區別,見我的前一篇文章。
- layout viewport 包含了頁面中的所有內容,瀏覽器已經計算好了layout viewport中的所有樣式。
- visual viewport 使用者看到的的瀏覽器視窗。使用者可能需要不斷移動visual viewport(滾動)才能看完layout viewport中的所有內容。
在這篇文章,我們從CSS2.1標準(主要是8、9、10、11章)出發,更加規範地討論這些內容。
initial containing block(layout viewport)
以前文章所說的layout viewport,在CSS標準中它被稱為
為了避免混淆,在這篇文章都使用visual viewport來指代瀏覽視窗。
initial containing block的尺寸有什麼用?它的尺寸可以決定<html>元素的尺寸。當<html>的寬度、高度、padding、margin使用百分數的值時,這個百分數的基準就是initial containing block的尺寸。
padding、margin使用百分數值的時候都是相對於containing block的width計算的,包括xxx-top、xxx-bottom!<html>元素是一個block element,與其他的block element一樣,它的寬度預設為containing block的100%(對於<html>就是initial containing block的100%),它的高度預設由子元素<body>撐開(除非明確設定了高度)。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <title>test</title> <style> * { padding: 0; margin: 0; box-sizing: border-box; } html, body { /* 使html, body的尺寸始終與visual viewport相同(即使你縮放、調整瀏覽器視窗的大小) 對於預設為block的元素可以省略width: 100%; */ width: 100%; height: 100%; } html { /* 相對於initial containing block計算百分比 */ padding-left: 50%; } #box { /* 填滿body元素,方便看出body的大小 */ width: 100%; height: 100%; /* 為什麼直接通過在body上應用background-color來標記它的大小? 因為body上使用background會有一個詭異的現象:background會超出body覆蓋整個頁面。 https://css-tricks.com/just-one-of-those-weird-things-about-css-background-on-body/ */ background-color: aqua; } </style> </head> <body> <div id="box"> </div> </body> </html>
在討論layout viewport(initial containing block)、visual viewport的尺寸的時候,我們應該使用CSS畫素為單位,而不是裝置獨立畫素。因為我們關心的是它們能容納多大的元素、多少個元素。
縮放、調整瀏覽器視窗大小的時候,會改變visual viewport可容納的CSS畫素數量:
- 在調整縮放比例的時候,瀏覽器視窗可容納的裝置獨立畫素數量不變,而CSS畫素的大小改變了,因此visual viewport可容納的CSS畫素數量也改變;
- 在調整瀏覽器視窗大小的時候,CSS畫素的大小不變,而瀏覽器視窗可容納的裝置獨立畫素數量改變了,因此visual viewport可容納的CSS畫素數量也改變。
而在桌面瀏覽器中,layout viewport(initial containing block)保持與visual viewport尺寸相同(可容納的CSS畫素數量相同),因此layout viewport的尺寸也會隨著縮放、調整瀏覽器視窗大小而改變。例子+註釋:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html,
body,
main {
/* 對於block元素其實可以省略width: 100%。
放在這裡只是為了強調一下,通過級聯的width:100%,main的寬度始終等於visual viewport的寬度。
如果你縮小瀏覽器視窗的寬度,main的寬度(以CSS畫素或裝置獨立畫素為單位)也會(響應式地)減小,從而會增加更多的換行以便容納內部的div.ilbk。
如果你增加縮放比例(通過Ctrl+滑鼠滾輪),main的寬度(以CSS畫素為單位)也會(響應式地)減小,從而會增加更多的換行以便容納內部的div.ilbk。 */
width: 100%;
}
.ilbk {
display: inline-block;
width: 200px;
height: 50px;
background-color: aquamarine;
}
</style>
</head>
<body>
<main>
<div class="ilbk"></div>
<div class="ilbk"></div>
<div class="ilbk"></div>
<div class="ilbk"></div>
<div class="ilbk"></div>
<div class="ilbk"></div>
<div class="ilbk"></div>
<div class="ilbk"></div>
<div class="ilbk"></div>
<div class="ilbk"></div>
<div class="ilbk"></div>
</main>
</body>
</html>
以上例子中,通過級聯的百分數寬度做到了響應式寬度,即,元素的寬度由客戶端的寬度動態決定(在這個例子中是<main>元素),而不是寫死在CSS中。用桌面瀏覽器開啟以上例子,隨便改變瀏覽器視窗大小、改變縮放比例,你會發現<main>的寬度(以CSS畫素為單位)會隨之改變:
在移動端瀏覽器上,情況有一些不同:現在大部分的移動端瀏覽器都有2種模式:“檢視桌面版網站”和“檢視移動版網站”:
- 在“檢視桌面版網站”模式下,瀏覽器會將layout viewport的設定為一個預定義尺寸,寬度一般是980個CSS畫素,高度一般是1500以上,不管visual viewport的尺寸是多少。
- 在“檢視移動版網站”模式下(預設處於這個模式),瀏覽器瀏覽器會根據viewport meta tag的資訊來決定layout viewport的尺寸。如果沒有viewport meta tag,則表現與“檢視桌面版網站”模式相同。
常用的viewport meta tag是<meta name="viewport" content="width=device-width, initial-scale=1.0">
。它告訴“檢視移動版網站”模式下的瀏覽器,將layout viewport的寬度(CSS畫素)設為裝置的寬度(裝置獨立畫素,一般是360px左右)。這樣,在縮放為100%的情況下(CSS畫素大小=裝置獨立畫素大小),螢幕恰好能裝下layout viewport,從而不會出現橫向滾動條。
另外,使用media query查詢width、height的時候(比如@media screen and (max-width: 500px) {...}
),查到的是layout viewport的尺寸,並且px指的是CSS畫素。在桌面端和瀏覽器端都是如此。例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test1</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html,
body,
main {
/* 對於block元素其實可以省略width: 100%。
放在這裡只是為了強調一下,通過級聯的width:100%,main的寬度始終等於visual viewport的寬度。
如果你縮小瀏覽器視窗的寬度,main的寬度(以CSS畫素或裝置獨立畫素為單位)也會(響應式地)減小,從而會增加更多的換行以便容納內部的div.ilbk。
如果你增加縮放比例(通過Ctrl+滑鼠滾輪),main的寬度(以CSS畫素為單位)也會(響應式地)減小,從而會增加更多的換行以便容納內部的div.ilbk。 */
width: 100%;
height: 100%;
background-color: aquamarine;
}
@media screen and (max-width: 500px) {
main {
background-color: purple;
}
}
</style>
</head>
<body>
<main>
</main>
</body>
</html>
你會發現,在這個例子中,通過改變瀏覽器視窗大小或者改變縮放比例,都能造成媒體查詢結果的改變。前面已經解釋過了,這兩個操作都會造成layout viewport尺寸的改變。
為了讓讀者明白meta viewport、媒體查詢出現的原因,這裡舉一個例子:有很多網站沒有針對移動端進行優化。對於這些網站,如果在移動端上將layout viewport的尺寸設定為visual viewport的尺寸(寬度為360CSS畫素左右),那麼排版可能會完全亂掉(意料之外的換行、溢位)。為了能正確顯示這種網站的排版,如果沒有meta viewport的指示,移動端瀏覽器將layout viewport的尺寸設為與電腦瀏覽器一樣,比如980px(單位:CSS畫素)。由於手機的螢幕邏輯畫素寬度一般只有300~400邏輯畫素,因此需要將多個css畫素由1個邏輯畫素顯示(也就是縮小,不要忘記縮放比例=css畫素邊長/邏輯畫素邊長
),通過縮小css畫素讓手機螢幕顯示的css畫素與網頁的css畫素一樣多。
但是這會引發一個問題:字型小得難以閱讀。使用者閱讀的時候又不得不用手指將縮放比例調整到100%左右(一個裝置獨立畫素顯示一個css畫素,對於我的手機來說,水平方向只有360個裝置獨立畫素),這個時候visual viewport只顯示layout viewport的一部分了。閱讀的時候需要橫向、縱向滾動。雖然能夠閱讀網站內容,但這依然是一種非常差的使用者體驗。
適配移動端的時候,先使用<meta name="viewport" content="width=device-width, initial-scale=1.0">
來定義layout viewport的寬度,然後通過媒體查詢來為不同的layout viewport定義不同的CSS排版。以下是瀏覽的效果(使用“檢視移動版網站”模式):可以看出,現在的字型大小合適了。網頁的排版變化了,更加適合移動端上的閱讀。
visual viewport
通過前面的說明你應該已經知道visual viewport了:它只是一個螢幕上的一個“視窗”,使用者通過這個視窗來觀察頁面。visual viewport就像一臺攝像機,layout viewport就像一張紙,攝像機對準紙的哪個部分,你就能看見哪個部分。你可以改變攝像機的視角大小(調整瀏覽器視窗大小),也可以調整攝像機的距離(調整縮放比例),這些方法都可以改變visual viewport,但是layout viewport始終不變(紙始終是那張紙)。
相關屬性
1. screen.width/height
上一篇文章說過的screen.width/height:整個螢幕的寬度和高度。這兩個數值的單位是裝置獨立畫素。這兩個數值不隨頁面縮放、瀏覽器視窗大小而改變,在前端開發的過程中可以認為是固定不變的(除非你通過作業系統改變螢幕的解析度)。這兩個數值是作業系統決定的,由於裝置獨立畫素比裝置畫素經常不等於1:1,實際螢幕物理畫素的解析度不一定是screen.width×screen.height。在上圖中列出了iphone各個手機的裝置解析度和IOS決定的解析度(邏輯解析度),我們只需要看這兩行。
裝置解析度就是螢幕上的物理畫素的數量,當手機廠商宣傳自己的螢幕有多麼清晰銳利的時候,相互攀比的就是這個數值。
邏輯解析度就是screen.width/height。為什麼iphone3GS以後的iphone都要把這個值設為實際螢幕解析度的1/2或1/3呢?因為隨著螢幕上塞進越來越多的真實畫素,螢幕大小的變化卻不那麼明顯,因此畫素密度也越來越高。如果還讓邏輯解析度:真實螢幕解析度=1:1,那麼12px的字型就會越來越小,影響閱讀體驗。因此,後續的iphone用4個真實畫素(甚至9個畫素)組合成一個“邏輯畫素”。這樣,即使真實畫素越來越小,每一個“邏輯畫素”的大小變化不大。瀏覽器可以放心地使用邏輯畫素來衡量大小,而不用擔心真實大小在不同的顯示器上出現嚴重偏差。
2. window.innerWidth/Height
visual viewport的大小,也就是瀏覽器內容視窗的大小,不包括選單欄、位址列、狀態列等,但是包括滾動條。單位是CSS畫素。通過這個屬性你可以知道,當前的瀏覽器視窗可以容納多少個css畫素。當用戶放大的時候這個數值會減少(因為css畫素變大了),當用戶縮小的時候這個數值增大。縮放改變瀏覽器視窗都會改變這個屬性的值。
Opera瀏覽器是例外,其單位是裝置獨立畫素,也就是說如果你只調整縮放,這兩個值不會改變。與之對應的,window.outerWidth/outerHeight給出整個瀏覽器視窗的大小(包括各種欄),但是單位是裝置獨立畫素。
3. document.documentElement.clientWidth/Height
Layout Viewport的尺寸(Layout Viewport是<html>元素的父容器),單位是CSS畫素。
它與window.innerWidth/Height的唯一區別是,document.documentElement.clientWidth/Height不包含滾動條。document.documentElement指的是html元素,通常Element.clientWidth應該給出元素的大小,但是document.documentElement.clientWidth/Height並不衡量html元素的大小,這是一個特例。
4. document.documentElement.offsetWidth/Height
<html>元素的尺寸,除非你在css中改變html的大小(很少有人這麼做),否則html的寬度始終與Layout Viewport寬度相同。單位是CSS畫素。
5. window.pageX/YOffset
滾動距離,描述使用者已經向右、向下滾動了多少個畫素,也可以理解為visual viewport相對於layout viewport的偏移值。單位是CSS畫素。
當用戶進行縮放的時候,瀏覽器會儘量保證:原先在內容區頂部的元素,在縮放以後依然在內容區頂部,看以下例子:原本數字3在頂部,放大後3依然在頂部。window.pageYOffset大致相同。大致相同的原因是CSS畫素數量不隨著縮放而變化,原本在上方的內容高度有多少個CSS畫素,放縮以後依然是多少個CSS畫素。至於為什麼不是完全相同,是因為"原先在內容區頂部的元素,在縮放以後依然在內容區頂部"這一機制無法完美地做到。
以下是測試用的程式碼,可以自己改動試試看(在電腦上使用chrome可以模擬不同的移動裝置來除錯,在開發者工具點選右上角的“toggle device toolbar”即可)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!--<link rel="stylesheet" href="test.css" type="text/css" />-->
<title>test viewport</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.box {
width: 100%;
height: 200px;
background-color: greenyellow;
}
.out {
position: absolute;
right: -30px;
background-color: rosybrown;
}
</style>
</head>
<body>
<div class="box">box</div>
<div class="out">out</div>
</body>
</html>
謝謝閱讀!後續我會更新更多有關響應式設計的內容!