【響應式佈局】initial containing block、viewport以及相關尺寸

前言

本篇文章修改、整理自我以前寫的一篇文章

在閱讀這篇文章之前,你需要了解設備像素、邏輯像素(設備獨立像素)和CSS像素的區別,見我的前一篇文章理解設備像素、設備獨立像素和css像素

在經典文章A tale of two viewports中,作者定義了兩種視口:

  1. layout viewport 包含了頁面中的所有內容,瀏覽器已經計算好了layout viewport中的所有樣式。
  2. visual viewport 用戶看到的的瀏覽窗口(在CSS標準中被稱爲viewport)。如果頁面內容溢出了visual viewport,用戶需要移動visual viewport(滾動)才能看完頁面中的所有內容。visual viewport只是一個屏幕上的一個“窗口”,用戶通過這個窗口來觀察頁面。

    溢出、滾動條的原理,我總結在了另一篇文章中:css溢出機制探究

在討論layout viewport、visual viewport的尺寸的時候,我們應該使用CSS像素爲單位,而不是設備獨立像素。因爲我們關心的是它們能容納多大的元素、多少個元素,這些元素的大小都是通過CSS來定義的。

在這篇文章,我們從CSS2.1標準(主要是8、9、10、11章)出發,更加規範地討論這些內容。

initial containing block(layout viewport)與 visual viewport

首先需要先了解一下containing block。containing block影響着其中元素的尺寸和定位。比如我們都知道position:absolute的元素是相對於【最近已定位祖先】來定位的,其背後的原因是:這個元素的盒子(box)的containing block由【最近已定位祖先的padding edge】產生。詳見MDNLayout and the containing block

在CSS標準中,<html>元素的containing block稱爲initial containing block。其他文章所說的layout viewport其實就是initial containing block。後面我將混用這兩個詞。

initial containing block的尺寸

initial containing block的尺寸有什麼用?它可以決定<html>元素的尺寸。當<html>的寬度、高度、padding、margin使用百分數的值時,這個百分數的基準就是initial containing block的尺寸。

paddingmargin使用百分數值的時候都是相對於containing block的width計算的,包括xxx-topxxx-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種模式:“查看桌面版網站”和“查看移動版網站”:

  • 在“查看桌面版網站”模式下,瀏覽器會將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,從而不會出現橫向滾動條。

可以看出,在移動端瀏覽器,不管處於哪種模式,不管有沒有viewport meta tag,layout viewport的尺寸在加載以後就固定了。

內容可以溢出 initial containing block(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像素數量:

  • 在調整縮放比例的時候,瀏覽器窗口可容納的設備獨立像素數量不變,而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像素爲單位)會隨之改變:

移動端瀏覽器

在移動端瀏覽器,不管處於哪種模式,不管有沒有viewport meta tag,layout viewport的尺寸在頁面加載以後就固定了。無論用戶如何縮放、調整瀏覽器窗口大小(這在手機上似乎做不到),layout viewport的尺寸都不會改變。

有一個例外:用戶可以在加載好頁面以後切換橫屏、豎屏模式,從而meta viewport tag中的device-width發生改變,從而layout viewport寬度改變。

media query

使用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的一部分了。閱讀的時候需要橫向、縱向滾動。
visual viewport只顯示layout viewport的一部分
雖然能夠閱讀網站內容,但這依然是一種非常差的用戶體驗。

適配移動端的時候,先使用<meta name="viewport" content="width=device-width, initial-scale=1.0">來定義layout viewport的寬度,然後通過媒體查詢來爲不同的layout viewport定義不同的CSS排版。以下是瀏覽的效果(使用“查看移動版網站”模式):
設置了viewport meta標籤以後
可以看出,現在的字體大小合適了。網頁的排版變化了,更加適合移動端上的閱讀。


相關屬性

1. screen.width/height

上一篇文章說過的screen.width/height:整個屏幕的寬度和高度。這兩個數值的單位是設備獨立像素。這兩個數值不隨頁面縮放、瀏覽器窗口大小而改變,在前端開發的過程中可以認爲是固定不變的(除非你通過操作系統改變屏幕的分辨率)。這兩個數值是操作系統決定的,由於設備獨立像素:設備像素經常不等於1:1,實際屏幕物理像素的分辨率不一定是screen.width×screen.height。
iphone的實際分辨率和瀏覽器使用的分辨率
在上圖中列出了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像素變大了),當用戶縮小的時候這個數值增大。縮放改變瀏覽器窗口都會改變這個屬性的值

與之對應的,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的width(很少有人這麼做),否則html的寬度始終與Layout Viewport寬度相同。單位是CSS像素。<html>元素的高度由內容撐開。

5. window.pageX/YOffset

滾動距離,描述用戶已經向右、向下滾動了多少個像素,也可以理解爲visual viewport相對於layout viewport的偏移值。單位是CSS像素

當用戶進行縮放的時候,瀏覽器會儘量保證:原先在內容區頂部的元素,在縮放以後依然在內容區頂部,看以下例子:
放大前
放大後
原本數字3在頂部,放大後3依然在頂部。window.pageYOffset大致相同。大致相同的原因是CSS像素數量不隨着縮放而變化,原本在上方的內容高度有多少個CSS像素,放縮以後依然是多少個CSS像素。至於爲什麼不是完全相同,是因爲"原先在內容區頂部的元素,在縮放以後依然在內容區頂部"這一機制無法完美地做到。

相關規範的進展

一些比css2.1更新的文檔(但是還沒有正式作爲Recommondation規範):

  1. CSS Snapshot CSS3開始,CSS不再由一份大而全的文檔來定義,而是分成多個模塊、由多個文檔來定義,方便各個技術的獨立演化。這份文檔收集了當前隸屬於CSS的、相對穩定的文檔。
  2. CSS Box Model Module Level 3 盒模型文檔。該文檔的內容與CSS2.1相比沒有變化
  3. CSS Positioned Layout Module Level 3 佈局、層疊文檔。
  4. CSS Display Module Level 3 CSS formatting box tree文檔。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章