1. 程式人生 > >響應式設計——initial containing block、viewport以及相關尺寸

響應式設計——initial containing block、viewport以及相關尺寸

在閱讀這篇文章之前,你需要了解裝置畫素、邏輯畫素(裝置獨立畫素)和CSS畫素的區別,見我的前一篇文章

  1. layout viewport 包含了頁面中的所有內容,瀏覽器已經計算好了layout viewport中的所有樣式。
  2. visual viewport 使用者看到的的瀏覽器視窗。使用者可能需要不斷移動visual viewport(滾動)才能看完layout viewport中的所有內容。

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

initial containing block(layout viewport)

以前文章所說的layout viewport,在CSS標準中它被稱為

initial containing block,它是<html>元素的父容器。在桌面瀏覽器中,layout viewport的尺寸等於visual viewport(即瀏覽視窗,在CSS標準中被稱為viewport)的尺寸

為了避免混淆,在這篇文章都使用visual viewport來指代瀏覽視窗。

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

padding
margin使用百分數值的時候都是相對於containing block的width計算的,包括xxx-topxxx-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的一部分了。閱讀的時候需要橫向、縱向滾動。visual viewport只顯示layout viewport的一部分雖然能夠閱讀網站內容,但這依然是一種非常差的使用者體驗。

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

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的實際解析度和瀏覽器使用的解析度在上圖中列出了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>

謝謝閱讀!後續我會更新更多有關響應式設計的內容!