響應式佈局的常用解決方案對比(媒體查詢、百分比、rem和vw/vh)

yuxiaoliang發表於2018-07-02

我的部落格原文地址:原文地址 如果文章對您有幫助,您的star是對我最好的鼓勵~


簡要介紹:前端開發中,靜態網頁通常需要適應不同解析度的裝置,常用的自適應解決方案包括媒體查詢、百分比、rem和vw/vh等。本文從px單位出發,分析了px在移動端佈局中的不足,接著介紹了幾種不同的自適應解決方案。

  • px和視口
  • 媒體查詢
  • 百分比
  • 自適應場景下的rem解決方案
  • 通過vw/vh來實現自適應

一、px和視口

在靜態網頁中,我們經常用畫素(px)作為單位,來描述一個元素的寬高以及定位資訊。在pc端,通常認為css中,1px所表示的真實長度是固定的。

那麼,px真的是一個裝置無關,跟長度單位米和分米一樣是固定大小的嗎?

答案是否定的,下面圖1.1和圖1.2分別表示pc端下和移動端下的顯示結果,在網頁中我們設定的font-size統一為16px。

px_text_pc

圖1.1 pc端下font-size為16px時的顯示結果

px_text_mobile

圖1.2 移動端下font-size為16px時的顯示結果

從上面兩幅圖的對比可以看出,字型都是16px,顯然在pc端中文字正常顯示,而在移動端文字很小,幾乎看不到,說明在css中1px並不是固定大小,直觀從我們發現在移動端1px所表示的長度較小,所以導致文字顯示不清楚。

那麼css中的1px的真實長度到底由什麼決定呢?

為了理清楚這個概念我們首先介紹畫素和視口的概念

1. 畫素

畫素是網頁佈局的基礎,一個畫素表示了計算機螢幕所能顯示的最小區域,畫素分為兩種型別:css畫素和物理畫素。

我們在js或者css程式碼中使用的px單位就是指的是css畫素,物理畫素也稱裝置畫素,只與裝置或者說硬體有關,同樣尺寸的螢幕,裝置的密度越高,物理畫素也就越多。下表表示css畫素和物理畫素的具體區別:

css畫素 為web開發者提供,在css中使用的一個抽象單位
物理畫素 只與裝置的硬體密度有關,任何裝置的物理畫素都是固定的

那麼css畫素與物理畫素的轉換關係是怎麼樣的呢?為了明確css畫素和物理畫素的轉換關係,必須先了解視口是什麼。

2. 視口

廣義的視口,是指瀏覽器顯示內容的螢幕區域,狹義的視口包括了佈局視口、視覺視口和理想視口

(1) 佈局視口(layout viewport)

佈局視口定義了pc網頁在移動端的預設佈局行為,因為通常pc的解析度較大,佈局視口預設為980px。也就是說在不設定網頁的viewport的情況下,pc端的網頁預設會以佈局視口為基準,在移動端進行展示。因此我們可以明顯看出來,預設為佈局視口時,根植於pc端的網頁在移動端展示很模糊。

(2) 視覺視口(visual viewport)

視覺視口表示瀏覽器內看到的網站的顯示區域,使用者可以通過縮放來檢視網頁的顯示內容,從而改變視覺視口。視覺視口的定義,就像拿著一個放大鏡分別從不同距離觀察同一個物體,視覺視口僅僅類似於放大鏡中顯示的內容,因此視覺視口不會影響佈局視口的寬度和高度。

(3) 理想視口(ideal viewport)

理想視口或者應該全稱為“理想的佈局視口”,在移動裝置中就是指裝置的解析度。換句話說,理想視口或者說解析度就是給定裝置物理畫素的情況下,最佳的“佈局視口”。

上述視口中,最重要的是要明確理想視口的概念,在移動端中,理想視口或者說解析度跟物理畫素之間有什麼關係呢?

為了理清解析度和物理畫素之間的聯絡,我們介紹一個用DPR(Device pixel ratio)裝置畫素比來表示,則可以寫成:

1 DPR = 物理畫素/解析度
複製程式碼

在不縮放的情況下,一個css畫素就對應一個dpr,也就是說,在不縮放

1 CSS畫素 = 物理畫素/解析度
複製程式碼

此外,在移動端的佈局中,我們可以通過viewport元標籤來控制佈局,比如一般情況下,我們可以通過下述標籤使得移動端在理想視口下佈局:

<meta id="viewport" name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1; user-scalable=no;">
複製程式碼

上述meta標籤的每一個屬性的詳細介紹如下:

屬性名 取值 描述
width 正整數 定義佈局視口的寬度,單位為畫素
height 正整數 定義佈局視口的高度,單位為畫素,很少使用
initial-scale [0,10] 初始縮放比例,1表示不縮放
minimum-scale [0,10] 最小縮放比例
maximum-scale [0,10] 最大縮放比例
user-scalable yes/no 是否允許手動縮放頁面,預設值為yes

其中我們來看width屬性,在移動端佈局時,在meta標籤中我們會將width設定稱為device-width,device-width一般是表示解析度的寬,通過width=device-width的設定我們就將佈局視口設定成了理想的視口。

3. px與自適應

上述我們瞭解到了當通過viewport元標籤,設定佈局視口為理想視口時,1個css畫素可以表示成:

1 CSS畫素 = 物理畫素/解析度
複製程式碼

我們直到,在pc端的佈局視口通常情況下為980px,移動端以iphone6為例,解析度為375 * 667,也就是說佈局視口在理想的情況下為375px。比如現在我們有一個750px * 1134px的視覺稿,那麼在pc端,一個css畫素可以如下計算:

PC端: 1 CSS畫素 = 物理畫素/解析度 = 750 / 980 =0.76 px
複製程式碼

而在iphone6下:

iphone6:1 CSS畫素 = 物理畫素 /解析度 = 750 / 375 = 2 px
複製程式碼

也就是說在PC端,一個CSS畫素可以用0.76個物理畫素來表示,而iphone6中 一個CSS畫素表示了2個物理畫素。此外不同的移動裝置解析度不同,也就是1個CSS畫素可以表示的物理畫素是不同的,因此如果在css中僅僅通過px作為長度和寬度的單位,造成的結果就是無法通過一套樣式,實現各端的自適應。

二、媒體查詢

在前面我們說到,不同端的裝置下,在css檔案中,1px所表示的物理畫素的大小是不同的,因此通過一套樣式,是無法實現各端的自適應。由此我們聯想:

如果一套樣式不行,那麼能否給每一種裝置各一套不同的樣式來實現自適應的效果?

答案是肯定的。

使用@media媒體查詢可以針對不同的媒體型別定義不同的樣式,特別是響應式頁面,可以針對不同螢幕的大小,編寫多套樣式,從而達到自適應的效果。舉例來說:

@media screen and (max-width: 960px){
    body{
      background-color:#FF6699
    }
}

@media screen and (max-width: 768px){
    body{
      background-color:#00FF66;
    }
}

@media screen and (max-width: 550px){
    body{
      background-color:#6633FF;
    }
}

@media screen and (max-width: 320px){
    body{
      background-color:#FFFF00;
    }
}
複製程式碼

上述的程式碼通過媒體查詢定義了幾套樣式,通過max-width設定樣式生效時的最大解析度,上述的程式碼分別對解析度在0~320px,320px~550px,550px~768px以及768px~960px的螢幕設定了不同的背景顏色。

通過媒體查詢,可以通過給不同解析度的裝置編寫不同的樣式來實現響應式的佈局,比如我們為不同解析度的螢幕,設定不同的背景圖片。比如給小螢幕手機設定@2x圖,為大螢幕手機設定@3x圖,通過媒體查詢就能很方便的實現。

但是媒體查詢的缺點也很明顯,如果在瀏覽器大小改變時,需要改變的樣式太多,那麼多套樣式程式碼會很繁瑣。

三、百分比

除了用px結合媒體查詢實現響應式佈局外,我們也可以通過百分比單位 " % " 來實現響應式的效果。

比如當瀏覽器的寬度或者高度發生變化時,通過百分比單位,通過百分比單位可以使得瀏覽器中的元件的寬和高隨著瀏覽器的變化而變化,從而實現響應式的效果。

為了瞭解百分比佈局,首先要了解的問題是:

css中的子元素中的百分比(%)到底是誰的百分比?

直觀的理解,我們可能會認為子元素的百分比完全相對於直接父元素,height百分比相對於height,width百分比相對於width。當然這種理解是正確的,但是根據css的盒式模型,除了height、width屬性外,還具有padding、border、margin等等屬性。那麼這些屬性設定成百分比,是根據父元素的那些屬性呢?此外還有border-radius和translate等屬性中的百分比,又是相對於什麼呢?下面來具體分析。

1. 百分比的具體分析

(1)子元素height和width的百分比

子元素的height或width中使用百分比,是相對於子元素的直接父元素,width相對於父元素的width,height相對於父元素的height。比如:

<div class="parent">
  <div class="child"></div>
</div>
複製程式碼

如果設定: .father{ width:200px; height:100px; } .child{ width:50%; height:50%; } 展示的效果為: 2018-06-22 7 00 29

(2) top和bottom 、left和right

子元素的top和bottom如果設定百分比,則相對於直接非static定位(預設定位)的父元素的高度,同樣

子元素的left和right如果設定百分比,則相對於直接非static定位(預設定位的)父元素的寬度。

展示的效果為:

2018-06-22 7 42 14

(3)padding

子元素的padding如果設定百分比,不論是垂直方向或者是水平方向,都相對於直接父親元素的width,而與父元素的height無關。

舉例來說:

.parent{
  width:200px;
  height:100px;
  background:green;
}
.child{
  width:0px;
  height:0px;
  background:blue;
  color:white;
  padding-top:50%;
  padding-left:50%;
}
複製程式碼

展示的效果為:

2018-06-22 7 55 13

子元素的初始寬高為0,通過padding可以將父元素撐大,上圖的藍色部分是一個正方形,且邊長為100px,說明padding不論寬高,如果設定成百分比都相對於父元素的width。

(4)margin

跟padding一樣,margin也是如此,子元素的margin如果設定成百分比,不論是垂直方向還是水平方向,都相對於直接父元素的width。這裡就不具體舉例。

(5)border-radius

border-radius不一樣,如果設定border-radius為百分比,則是相對於自身的寬度,舉例來說:

  <div class="trangle"></div>
複製程式碼

設定border-radius為百分比:

.trangle{
  width:100px;
  height:100px;
  border-radius:50%;
  background:blue;
  margin-top:10px;
}
複製程式碼

展示效果為:

2018-06-22 8 09 20

除了border-radius外,還有比如translate、background-size等都是相對於自身的,這裡就不一一舉例。

2. 百分比單位佈局應用

百分比單位在佈局上應用還是很廣泛的,這裡介紹一種應用。

比如我們要實現一個固定長寬比的長方形,比如要實現一個長寬比為4:3的長方形,我們可以根據padding屬性來實現,因為padding不管是垂直方向還是水平方向,百分比單位都相對於父元素的寬度,因此我們可以設定padding-top為百分比來實現,長寬自適應的長方形:

<div class="trangle"></div>
複製程式碼

設定樣式讓其自適應:

.trangle{
  height:0;
  width:100%;
  padding-top:75%;
}
複製程式碼

通過設定padding-top:75%,相對比寬度的75%,因此這樣就設定了一個長寬高恆定比例的長方形,具體效果展示如下:

jest

3. 百分比單位缺點

從上述對於百分比單位的介紹我們很容易看出如果全部使用百分比單位來實現響應式的佈局,有明顯的以下兩個缺點:

(1)計算困難,如果我們要定義一個元素的寬度和高度,按照設計稿,必須換算成百分比單位。 (2)從小節1可以看出,各個屬性中如果使用百分比,相對父元素的屬性並不是唯一的。比如width和height相對於父元素的width和height,而margin、padding不管垂直還是水平方向都相對比父元素的寬度、border-radius則是相對於元素自身等等,造成我們使用百分比單位容易使佈局問題變得複雜。

四、自適應場景下的rem解決方案

1. rem單位

首先來看,什麼是rem單位。rem是一個靈活的、可擴充套件的單位,由瀏覽器轉化畫素並顯示。與em單位不同,rem單位無論巢狀層級如何,都只相對於瀏覽器的根元素(HTML元素)的font-size。預設情況下,html元素的font-size為16px,所以:

    1 rem = 12px
複製程式碼

為了計算方便,通常可以將html的font-size設定成:

    html{ font-size: 62.5% }
複製程式碼

這種情況下:

    1 rem = 10px
複製程式碼

2.通過rem來實現響應式佈局

rem單位都是相對於根元素html的font-size來決定大小的,根元素的font-size相當於提供了一個基準,當頁面的size發生變化時,只需要改變font-size的值,那麼以rem為固定單位的元素的大小也會發生響應的變化。 因此,如果通過rem來實現響應式的佈局,只需要根據檢視容器的大小,動態的改變font-size即可。

function refreshRem() {
    var docEl = doc.documentElement;
    var width = docEl.getBoundingClientRect().width;
    var rem = width / 10;
    docEl.style.fontSize = rem + 'px';
    flexible.rem = win.rem = rem;
}
win.addEventListener('resize', refreshRem);
複製程式碼

上述程式碼中將檢視容器分為10份,font-size用十分之一的寬度來表示,最後在header標籤中執行這段程式碼,就可以動態定義font-size的大小,從而1rem在不同的視覺容器中表示不同的大小,用rem固定單位可以實現不同容器內佈局的自適應。

3. rem2px和px2rem

如果在響應式佈局中使用rem單位,那麼存在一個單位換算的問題,rem2px表示從rem換算成px,這個就不說了,只要rem乘以相應的font-size中的大小,就能換算成px。更多的應用是px2rem,表示的是從px轉化為rem。

比如給定的視覺稿為750px(物理畫素),如果我們要將所有的佈局單位都用rem來表示,一種比較笨的辦法就是對所有的height和width等元素,乘以相應的比例,現將視覺稿換算成rem單位,然後一個個的用rem來表示。另一種比較方便的解決方法就是,在css中我們還是用px來表示元素的大小,最後編寫完css程式碼之後,將css檔案中的所有px單位,轉化成rem單位。

px2rem的原理也很簡單,重點在於預處理以px為單位的css檔案,處理後將所有的px變成rem單位。可以通過兩種方式來實現:

1) webpack loader的形式:

npm install px2rem-loader
複製程式碼

在webpack的配置檔案中:

module.exports = {
  // ...
  module: {
    rules: [{
      test: /\.css$/,
      use: [{
        loader: 'style-loader'
      }, {
        loader: 'css-loader'
      }, {
        loader: 'px2rem-loader',
        // options here
        options: {
          remUni: 75,
          remPrecision: 8
        }
      }]
    }]
  }
複製程式碼

}

2)webpack中使用postcss plugin

npm install postcss-loader
複製程式碼

在webpack的plugin中:

var px2rem = require('postcss-px2rem');

module.exports = {
  module: {
    loaders: [
      {
        test: /\.css$/,
        loader: "style-loader!css-loader!postcss-loader"
      }
    ]
  },
  postcss: function() {
    return [px2rem({remUnit: 75})];
  }
}
複製程式碼

4. rem 佈局應用舉例

網易新聞的移動端頁面使用了rem佈局,具體例子如下:

jest1

5. rem 佈局的缺點

通過rem單位,可以實現響應式的佈局,特別是引入相應的postcss相關外掛,免去了設計稿中的px到rem的計算。rem單位在國外的一些網站也有使用,這裡所說的rem來實現佈局的缺點,或者說是小缺陷是:

在響應式佈局中,必須通過js來動態控制根元素font-size的大小。

也就是說css樣式和js程式碼有一定的耦合性。且必須將改變font-size的程式碼放在css樣式之前。

五. 通過vw/vh來實現自適應

1. 什麼是vw/vh ?

css3中引入了一個新的單位vw/vh,與檢視視窗有關,vw表示相對於檢視視窗的寬度,vh表示相對於檢視視窗高度,除了vw和vh外,還有vmin和vmax兩個相關的單位。各個單位具體的含義如下:

單位 含義
vw 相對於視窗的寬度,視窗寬度是100vw
vh 相對於視窗的高度,視窗高度是100vh
vmin vw和vh中的較小值
vmax vw和vh中的較大值

這裡我們發現視窗寬高都是100vw/100vh,那麼vw或者vh,下簡稱vw,很類似百分比單位。vw和%的區別為:

單位 含義
% 大部分相對於祖先元素,也有相對於自身的情況比如(border-radius、translate等)
vw/vh 相對於視窗的尺寸

從對比中我們可以發現,vw單位與百分比類似,單確有區別,前面我們介紹了百分比單位的換算困難,這裡的vw更像"理想的百分比單位"。任意層級元素,在使用vw單位的情況下,1vw都等於檢視寬度的百分之一。

2. vw單位換算

同樣的,如果要將px換算成vw單位,很簡單,只要確定檢視的視窗大小(佈局視口),如果我們將佈局視口設定成解析度大小,比如對於iphone6/7 375*667的解析度,那麼px可以通過如下方式換算成vw:

1px = (1/375)*100 vw
複製程式碼

此外,也可以通過postcss的相應外掛,預處理css做一個自動的轉換,postcss-px-to-viewport可以自動將px轉化成vw。 postcss-px-to-viewport的預設引數為:

var defaults = {
  viewportWidth: 320,
  viewportHeight: 568, 
  unitPrecision: 5,
  viewportUnit: 'vw',
  selectorBlackList: [],
  minPixelValue: 1,
  mediaQuery: false
};
複製程式碼

通過指定視窗的寬度和高度,以及換算精度,就能將px轉化成vw。

3. vw/vh單位的相容性

可以在https://caniuse.com/ 檢視各個版本的瀏覽器對vw單位的支援性。

2018-06-27 8 19 53

從上圖我們發現,絕大多數的瀏覽器支援vw單位,但是ie9-11不支援vmin和vmax,考慮到vmin和vmax單位不常用,vw單位在絕大部分高版本瀏覽器內的支援性很好,但是opera瀏覽器整體不支援vw單位,如果需要相容opera瀏覽器的佈局,不推薦使用vw。

小結:本文介紹在佈局中常用的單位,比如px、%、rem和vw等等,以及不同的單位在響應式佈局中的優缺點。

相關文章