本篇文章修改、整理自我之前寫的一篇文章。css
在閱讀這篇文章以前,你須要瞭解設備像素、邏輯像素(設備獨立像素)和CSS像素的區別,見個人前一篇文章理解設備像素、設備獨立像素和css像素。html
在經典文章A tale of two viewports中,做者定義了兩種視口:前端
visual viewport 用戶看到的的瀏覽窗口(在CSS標準中被稱爲viewport)。若是頁面內容溢出了visual viewport,用戶須要移動visual viewport(滾動)才能看完頁面中的全部內容。visual viewport只是一個屏幕上的一個「窗口」,用戶經過這個窗口來觀察頁面。segmentfault
溢出、滾動條的原理,我總結在了另外一篇文章中: css溢出機制探究。
在討論layout viewport、visual viewport的尺寸的時候,咱們應該使用CSS像素爲單位,而不是設備獨立像素。由於咱們關心的是它們能容納多大的元素、多少個元素,這些元素的大小都是經過CSS來定義的。瀏覽器
在這篇文章,咱們從CSS2.1標準(主要是八、九、十、11章)出發,更加規範地討論這些內容。less
首先須要先了解一下containing block。containing block影響着其中元素的尺寸和定位。好比咱們都知道position:absolute的元素是相對於【最近已定位祖先】來定位的,其背後的緣由是:這個元素的盒子(box)的containing block由【最近已定位祖先的padding edge】產生。詳見MDNLayout and the containing block。dom
在CSS標準中,<html>元素的containing block稱爲initial containing block。其餘文章所說的layout viewport其實就是initial containing block。後面我將混用這兩個詞。iphone
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>撐開(除非明確設置了高度)。
那麼initial containing block的尺寸是怎麼肯定的呢?字體
在桌面瀏覽器中,initial containing block的尺寸等於visual viewport的尺寸。
爲了不混淆,在這篇文章都使用visual viewport來指代瀏覽窗口。
如下例子驗證了,initial containing block的尺寸是等於瀏覽窗口的。而且咱們能夠利用它,來元素的width、height、padding(margin同理):
<!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的尺寸有一些不一樣:如今大部分的移動端瀏覽器都有2種模式:「查看桌面版網站」和「查看移動版網站」:
經常使用的viewport meta tag是<meta name="viewport" content="width=device-width, initial-scale=1.0">
。它告訴「查看移動版網站」模式下的瀏覽器,將layout viewport的寬度(CSS像素)設爲設備的寬度(設備獨立像素,通常是360px左右)。這樣,在縮放爲100%的狀況下(CSS像素大小=設備獨立像素大小),屏幕剛好能裝下layout viewport,從而不會出現橫向滾動條。
能夠看出,在移動端瀏覽器,無論處於哪一種模式,無論有沒有viewport meta tag,layout viewport的尺寸在加載之後就固定了。
不要以爲"initial containing block"名字聽起來很厲害,就確定會將全部內容包含在其區域內。就像其餘普通的containing block,頁面中的內容徹底能夠溢出它。好比絕對定位、overflow:visible。
例子:
<!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"> <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>
其中div.out就溢出了initial containint block的區域。
因爲有內容溢出了visual viewport,所以在visual viewport上出現了橫向滾動條。visual viewport上的滾動條在css溢出機制探究中討論。
縮放、調整瀏覽器窗口大小的時候,會改變visual viewport的尺寸(用可容納的CSS像素數量來衡量):
在桌面瀏覽器中,layout viewport(initial containing block)始終保持與visual viewport尺寸相同(這是爲了防止出現橫向滾動條,見我上一篇文章對page zoom的解釋),所以當你經過縮放、調整瀏覽器窗口大小來改變visual viewport的大小時,layout viewport(initial containing block)也會隨之改變。
好比,你在桌面端增大縮放比例,visual viewport會縮小,initial containing block隨之縮小,這就是爲何咱們在桌面端縮放可能會形成佈局錯亂。(順便提一下,這個問題的簡單解決方案是在HTML元素上設置min-width,防止HTML元素跟着initial containing block一塊兒變小,不過會出現橫向滾動條。複雜解決方案:移動端適配)
例子+註釋:
<!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像素爲單位)會隨之改變:
在移動端瀏覽器,無論處於哪一種模式,無論有沒有viewport meta tag,layout viewport的尺寸(以CSS像素爲單位)在頁面加載之後就固定了。不管用戶如何縮放、調整瀏覽器窗口大小(這在手機上彷佛作不到),layout viewport的尺寸都不會改變。
所以,無論你在移動端瀏覽器如何縮放,頁面佈局都不會改變。
「layout viewport的尺寸在頁面加載之後就固定了」,這個概括有一個例外:用戶能夠在加載好頁面之後切換橫屏、豎屏模式,從而meta viewport tag中的device-width發生改變,從而layout viewport寬度改變。
形成以上不一樣的緣由是,在桌面端的縮放和在移動端的縮放有不一樣的性質。見我在上一篇文章的討論。
使用media query查詢width、height的時候(好比@media screen and (max-width: 500px) {...}
),查到的是layout viewport的尺寸,而且px指的是CSS像素。在桌面端和移動端都是如此。
MDN 文檔也指出了這一點:... if the virtual viewport(也就是這裏所說的layout viewport) is 980px for example, media queries that kick in at 640px or 480px or less will never be used, limiting the effectiveness of such responsive design techniques.
例子:
<!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排版。如下是瀏覽的效果(使用「查看移動版網站」模式):
如今的字體大小合適了,網頁的排版變化了,沒有元素橫向溢出,沒有橫向滾動條,在移動端上的閱讀體驗更好。
上一篇文章說過的screen.width/height:整個屏幕的寬度和高度。這兩個數值的單位是設備獨立像素。這兩個數值不隨頁面縮放、瀏覽器窗口大小而改變,在前端開發的過程當中能夠認爲是固定不變的(除非你經過操做系統改變屏幕的分辨率)。這兩個數值是操做系統決定的,因爲設備獨立像素:設備像素常常不等於1:1,實際屏幕物理像素的分辨率不必定是screen.width×screen.height。
在上圖中列出了iphone各代的設備分辨率(物理分辨率)和邏輯分辨率,咱們只須要看這兩行。
設備分辨率就是屏幕上的物理像素的數量,當手機廠商宣傳本身的屏幕有多麼清晰銳利的時候,相互攀比的就是這個數值。
邏輯分辨率就是screen.width/height。爲何iphone3GS之後的iphone都要把這個值設爲實際屏幕分辨率的1/2或1/3呢?由於隨着屏幕上塞進愈來愈多的物理像素,屏幕大小的變化卻不那麼明顯,所以像素密度也愈來愈高。若是還讓邏輯分辨率:真實屏幕分辨率=1:1,那麼12px的字體就會愈來愈小,影響閱讀體驗。所以,後續的iphone用4個物理像素(甚至9個像素)組合成一個「邏輯像素」。這樣,即便物理像素愈來愈小,每個「邏輯像素」的大小變化不大。瀏覽器能夠放心地使用邏輯像素來衡量大小,而不用擔憂真實大小在不一樣的顯示器上出現嚴重誤差。
visual viewport的大小,也就是瀏覽器內容窗口的大小,不包括菜單欄、地址欄、狀態欄等,可是包括滾動條。單位是CSS像素。經過這個屬性你能夠知道,當前的瀏覽器窗口能夠容納多少個css像素。當用戶放大的時候這個數值會減小(由於css像素變大了),當用戶縮小的時候這個數值增大。縮放改變瀏覽器窗口都會改變這個屬性的值。
與之對應的,window.outerWidth/outerHeight給出整個瀏覽器窗口的大小(包括各類欄),可是單位是 設備獨立像素。
Layout Viewport(initial containing block)的尺寸。注意,Layout Viewport沒有滾動條(根據css溢出機制探究中的討論,只有元素或者visual viewport才能擁有滾動條)。單位是CSS像素。
document.documentElement指的是html元素,一般 Element.clientWidth應該給出元素的內容區域的大小,可是document.documentElement.clientWidth/Height並不衡量html元素的大小,這是一個特例。各個瀏覽器都遵循着這個約定。而且, 這個約定正在被標準化。
<html>元素的尺寸。前面已經討論過<html>元素的尺寸是如何計算的了,默認狀況下<html>的寬度始終與Layout Viewport寬度相同。單位是CSS像素。<html>元素的高度由內容撐開。
滾動距離,描述visual viewport已經向右、向下滾動了多少個像素。也能夠理解爲visual viewport相對於layout viewport的偏移值。單位是CSS像素。
它們分別有1個別名(前者的兼容性更好些):
window.pageXOffset == window.scrollX; // always true window.pageYOffset == window.scrollY; // always true
此外,因爲Element上就有獲取內容滾動的scrollLeft、scrollTop屬性(全部Element均可以使用),所以還有:
window.pageXOffset === document.documentElement.scrollLeft; // always true window.pageYOffset === document.documentElement.scrollTop; // always true
當用戶進行縮放的時候,瀏覽器會 儘可能保證:原先在內容區頂部的元素,在縮放之後依然在內容區頂部,看如下例子:
放大前:
![]()
放大後:
![]()
本來數字3在頂部,放大後3依然在頂部。window.pageYOffset 大體相同。大體相同的緣由是CSS像素數量不隨着縮放而變化, 本來在上方的內容高度有多少個CSS像素,放縮之後依然是多少個CSS像素。至於 爲何不是徹底相同,是由於 "原先在內容區頂部的元素,在縮放之後依然在內容區頂部"這一機制沒法完美地作到。
一些比css2.1更新的文檔(可是尚未正式做爲Recommondation規範):