前端響應式佈局原理與方案(詳細版)

八叉樹發表於2019-04-08

引言

響應式佈局指的是同一頁面在不同螢幕尺寸下有不同的佈局。傳統的開發方式是PC端開發一套,手機端再開發一套,而使用響應式佈局只要開發一套就夠,缺點就是CSS比較重。下面是部落格網站對不同裝置適配後的結果,分別是iPhone5/SE,iphone6/7/8,iphone 6/7/8 plus,ipad pro,dell臺式寬屏(1440 X 900)

響應式設計與自適應設計的區別:響應式開發一套介面,通過檢測視口解析度,針對不同客戶端在客戶端做程式碼處理,來展現不同的佈局和內容;自適應需要開發多套介面,通過檢測視口解析度,來判斷當前訪問的裝置是pc端、平板、手機,從而請求服務層,返回不同的頁面。

前端響應式佈局原理與方案(詳細版)

前端響應式佈局原理與方案(詳細版)

前端響應式佈局原理與方案(詳細版)

響應式佈局的實現方案

1. 媒體查詢

CSS3媒體查詢可以讓我們針對不同的媒體型別定義不同的樣式,當重置瀏覽器視窗大小的過程中,頁面也會根據瀏覽器的寬度和高度重新渲染頁面。

如何選擇螢幕大小分割點

如何確定媒體查詢的分割點也是一個開發中會遇到的問題,下面是市場上的移動裝置和電腦螢幕解析度的分佈情況,可以發現不同品牌和型號的裝置螢幕解析度一般都不一樣

img

如果我們選擇600px,900px,1200px,1800px作為分割點,可以適配到常見的14個機型:

img

當然這只是其中的一種分割方案,我們還可以這樣劃分:480px,800px,1400px,1400px

img

而作為曾經典型的響應式佈局框架,Bootstrap是怎麼進行斷點的呢?

img

上面的分割方案不一定滿足專案中的實際需求,我們可以先用跨度大的分割點進行分割,如果出現不適配的情況可以再根據實際情況增加新的分割點。

移動優先 OR PC優先

不管是移動優先還是PC優先,都是依據當隨著螢幕寬度增大或減小的時候,後面的樣式會覆蓋前面的樣式。因此,移動端優先首先使用的是min-width,PC端優先使用的max-width

移動優先:

/* iphone6 7 8 */
body {
    background-color: yellow;
}
/* iphone 5 */
@media screen and (max-width: 320px) {
    body {
      background-color: red;
    }
}
/* iphoneX */
@media screen and (min-width: 375px) and (-webkit-device-pixel-ratio: 3) {
    body {
      background-color: #0FF000;
    }
}
/* iphone6 7 8 plus */
@media screen and (min-width: 414px) {
    body {
      background-color: blue;
    }
}
/* ipad */
@media screen and (min-width: 768px) {
    body {
      background-color: green;
    }
}
/* ipad pro */
@media screen and (min-width: 1024px) {
    body {
      background-color: #FF00FF;
    }
}
/* pc */
@media screen and (min-width: 1100px) {
    body {
      background-color: black;
    }
}
複製程式碼

PC優先:

/* pc width > 1024px */
    body {
        background-color: yellow;
    }
/* ipad pro */
@media screen and (max-width: 1024px) {
    body {
        background-color: #FF00FF;
    }
}
/* ipad */
@media screen and (max-width: 768px) {
    body {
        background-color: green;
    }
}
/* iphone6 7 8 plus */
@media screen and (max-width: 414px) {
    body {
        background-color: blue;
    }
}
/* iphoneX */
@media screen and (max-width: 375px) and (-webkit-device-pixel-ratio: 3) {
    body {
        background-color: #0FF000;
    }
}
/* iphone6 7 8 */
@media screen and (max-width: 375px) and (-webkit-device-pixel-ratio: 2) {
    body {
        background-color: #0FF000;
    }
}
/* iphone5 */
@media screen and (max-width: 320px) {
    body {
        background-color: #0FF000;
    }
}
複製程式碼

2.百分比佈局

通過百分比單位,可以使得瀏覽器中元件的寬和高隨著瀏覽器的高度的變化而變化,從而實現響應式的效果。Bootstrap裡面的柵格系統就是利用百分比來定義元素的寬高,CSS3支援最大最小高,可以將百分比和max(min)一起結合使用來定義元素在不同裝置下的寬高。

/* pc width > 1100px */
html, body { margin: 0;padding: 0;width: 100%;height: 100%;}
aside {
    width: 10%;
    height: 100%;
    background-color: red;
    float: left;
}
main {
    height: 100%;
    background-color: blue;
    overflow: hidden;
}
/* ipad pro */
@media screen and (max-width: 1024px) {
    aside {
      width: 8%;
      background-color: yellow;
    }
}
/* ipad */
@media screen and (max-width: 768px) {
    aside {
      float: none;
      width: 100%;
      height: 10%;
      background-color: green;
    }
    main {
      height: calc(100vh - 10%);
      background-color: red;
    }
}
/* iphone6 7 8 plus */
@media screen and (max-width: 414px) {
    aside {
      float: none;
      width: 100%;
      height: 5%;
      background-color: yellow;
    }
    main {
      height: calc(100vh - 5%);
      background-color: red;
    }
}
/* iphoneX */
@media screen and (max-width: 375px) and (-webkit-device-pixel-ratio: 3) {
    aside {
      float: none;
      width: 100%;
      height: 10%;
      background-color: blue;
    }
    main {
      height: calc(100vh - 10%);
      background-color: red;
    }
}
/* iphone6 7 8 */
@media screen and (max-width: 375px) and (-webkit-device-pixel-ratio: 2) {
    aside {
      float: none;
      width: 100%;
      height: 3%;
      background-color: black;
    }
    main {
      height: calc(100vh - 3%);
      background-color: red;
    }
}
/* iphone5 */
@media screen and (max-width: 320px) {
    aside {
      float: none;
      width: 100%;
      height: 7%;
      background-color: green;
    }
    main {
      height: calc(100vh - 7%);
      background-color: red;
    }
}
複製程式碼

但是我們必須要弄清楚css中子元素的百分比到底是相對誰的百分比。直接上結論吧:

子元素的heightwidth中使用百分比,是相對於子元素的直接父元素,width相對於父元素的widthheight相對於父元素的height;子元素的topbottom如果設定百分比,則相對於直接非static定位(預設定位)的父元素的高度,同樣子元素的leftright如果設定百分比,則相對於直接非static定位(預設定位的)父元素的寬度;子元素的padding如果設定百分比,不論是垂直方向或者是水平方向,都相對於直接父親元素的width,而與父元素的height無關。跟padding一樣,margin也是如此,子元素的margin如果設定成百分比,不論是垂直方向還是水平方向,都相對於直接父元素的widthborder-radius不一樣,如果設定border-radius為百分比,則是相對於自身的寬度,除了border-radius外,還有比如translatebackground-size等都是相對於自身的;

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

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

3.rem佈局

REMCSS3新增的單位,並且移動端的支援度很高,Android2.x+,ios5+都支援。rem單位都是相對於根元素html的font-size來決定大小的,根元素的font-size相當於提供了一個基準,當頁面的size發生變化時,只需要改變font-size的值,那麼以rem為固定單位的元素的大小也會發生響應的變化。 因此,如果通過rem來實現響應式的佈局,只需要根據檢視容器的大小,動態的改變font-size即可(而em是相對於父元素的)。

rem響應式的佈局思想:

  • 一般不要給元素設定具體的寬度,但是對於一些小圖示可以設定具體寬度值
  • 高度值可以設定固定值,設計稿有多大,我們就嚴格有多大
  • 所有設定的固定值都用rem做單位(首先在HTML總設定一個基準值:pxrem的對應比例,然後在效果圖上獲取px值,佈局的時候轉化為rem值)
  • js獲取真實螢幕的寬度,讓其除以設計稿的寬度,算出比例,把之前的基準值按照比例進行重新的設定,這樣專案就可以在移動端自適應了

rem佈局的缺點:

在響應式佈局中,必須通過js來動態控制根元素font-size的大小,也就是說css樣式和js程式碼有一定的耦合性,且必須將改變font-size的程式碼放在css樣式之前

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

REM佈局也是目前多螢幕適配的最佳方式。預設情況下我們html標籤的font-size為16px,我們利用媒體查詢,設定在不同裝置下的字型大小。

/* pc width > 1100px */
html{ font-size: 100%;}
body {
    background-color: yellow;
    font-size: 1.5rem;
}
/* ipad pro */
@media screen and (max-width: 1024px) {
    body {
      background-color: #FF00FF;
      font-size: 1.4rem;
    }
}
/* ipad */
@media screen and (max-width: 768px) {
    body {
      background-color: green;
      font-size: 1.3rem;
    }
}
/* iphone6 7 8 plus */
@media screen and (max-width: 414px) {
    body {
      background-color: blue;
      font-size: 1.25rem;
    }
}
/* iphoneX */
@media screen and (max-width: 375px) and (-webkit-device-pixel-ratio: 3) {
    body {
      background-color: #0FF000;
      font-size: 1.125rem;
    }
}
/* iphone6 7 8 */
@media screen and (max-width: 375px) and (-webkit-device-pixel-ratio: 2) {
    body {
      background-color: #0FF000;
      font-size: 1rem;
    }
}
/* iphone5 */
@media screen and (max-width: 320px) {
    body {
      background-color: #0FF000;
      font-size: 0.75rem;
    }
}
複製程式碼

4.視口單位

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

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

前端響應式佈局原理與方案(詳細版)

用視口單位度量,視口寬度為100vw,高度為100vh(左側為豎屏情況,右側為橫屏情況)。例如,在桌面端瀏覽器視口尺寸為650px,那麼 1vw = 650 * 1% = 6.5px(這是理論推算的出,如果瀏覽器不支援0.5px,那麼實際渲染結果可能是7px)。

那麼vw或者vh很類似百分比單位。vw和%的區別為:

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

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

使用視口單位來實現響應式有兩種做法:

1.僅使用vw作為CSS單位
  • 對於設計稿的尺寸轉換為為單位,我們使用Sass函式編譯

    //iPhone 6尺寸作為設計稿基準
    $vm_base: 375; 
    @function vw($px) {
        @return ($px / 375) * 100vw;
    }
    複製程式碼
  • 無論是文字還是佈局寬度、間距等都使用vw作為單位

    .mod_nav {
        background-color: #fff;
        &_list {
            display: flex;
            padding: vm(15) vm(10) vm(10); // 內間距
            &_item {
                flex: 1;
                text-align: center;
                font-size: vm(10); // 字型大小
                &_logo {
                    display: block;
                    margin: 0 auto;
                    width: vm(40); // 寬度
                    height: vm(40); // 高度
                    img {
                        display: block;
                        margin: 0 auto;
                        max-width: 100%;
                    }
                }
                &_name {
                    margin-top: vm(2);
                }
            }
        }
    }
    複製程式碼
  • 1物理畫素線(也就是普通螢幕下1px,高清螢幕下0.5px的情況)採用transform屬性scale實現

    .mod_grid {
        position: relative;
        &::after {
            // 實現1物理畫素的下邊框線
            content: '';
            position: absolute;
            z-index: 1;
            pointer-events: none;
            background-color: #ddd;
            height: 1px;
            left: 0;
            right: 0;
            top: 0;
            @media only screen and (-webkit-min-device-pixel-ratio: 2) {
                -webkit-transform: scaleY(0.5);
                -webkit-transform-origin: 50% 0%;
            }
        }
        ...
    }
    複製程式碼
  • 對於需要保持寬高比的圖,應該用padding-top實現

    .mod_banner {
        position: relative;
        padding-top: percentage(100/700); // 使用padding-top
        height: 0;
        overflow: hidden;
        img {
            width: 100%;
            height: auto;
            position: absolute;
            left: 0;
            top: 0; 
        }
    }
    複製程式碼
2.搭配vw和rem

雖然採用vw適配後的頁面效果很好,但是它是利用視口單位實現的佈局,依賴視口大小而自動縮放,無論視口過大還是過小,它也隨著時候過大或者過小,失去了最大最小寬度的限制,此時我們可以結合rem來實現佈局

  • 給根元素大小設定隨著視口變化而變化的vw單位,這樣就可以實現動態改變其大小

  • 限制根元素字型大小的最大最小值,配合body加上最大寬度和最小寬度

    // rem 單位換算:定為 75px 只是方便運算,750px-75px、640-64px、1080px-108px,如此類推
    $vm_fontsize: 75; // iPhone 6尺寸的根元素大小基準值
    @function rem($px) {
         @return ($px / $vm_fontsize ) * 1rem;
    }
    // 根元素大小使用 vw 單位
    $vm_design: 750;
    html {
        font-size: ($vm_fontsize / ($vm_design / 2)) * 100vw; 
        // 同時,通過Media Queries 限制根元素最大最小值
        @media screen and (max-width: 320px) {
            font-size: 64px;
        }
        @media screen and (min-width: 540px) {
            font-size: 108px;
        }
    }
    // body 也增加最大最小寬度限制,避免預設100%寬度的 block 元素跟隨 body 而過大過小
    body {
        max-width: 540px;
        min-width: 320px;
    }
    複製程式碼

5.圖片響應式

這裡的圖片響應式包括兩個方面,一個就是大小自適應,這樣能夠保證圖片在不同的螢幕解析度下出現壓縮、拉伸的情況;一個就是根據不同的螢幕解析度和裝置畫素比來儘可能選擇高解析度的圖片,也就是當在小螢幕上不需要高清圖或大圖,這樣我們用小圖代替,就可以減少網路頻寬了。

1.使用max-width(圖片自適應):

圖片自適應意思就是圖片能隨著容器的大小進行縮放,可以採用如下程式碼:

img {
    display: inline-block;
    max-width: 100%;
    height: auto;
}
複製程式碼

inline-block 元素相對於它周圍的內容以內聯形式呈現,但與內聯不同的是,這種情況下我們可以設定寬度和高度。 max-width保證了圖片能夠隨著容器的進行等寬擴充(即保證所有圖片最大顯示為其自身的 100%。此時,如果包含圖片的元素比圖片固有寬度小,圖片會縮放佔滿最大可用空間),而heightauto可以保證圖片進行等比縮放而不至於失真。如果是背景圖片的話要靈活運用background-size屬性。

那麼為什麼不能用width:100%呢?因為這條規則會導致它顯示得跟它的容器一樣寬。在容器比圖片寬得多的情況下,圖片會被無謂地拉伸。

2.使用srcset
<img srcset="photo_w350.jpg 1x, photo_w640.jpg 2x" src="photo_w350.jpg" alt="">
複製程式碼

如果螢幕的dpi = 1的話則載入1倍圖,而dpi = 2則載入2倍圖,手機和mac基本上dpi都達到了2以上,這樣子對於普通螢幕來說不會浪費流量,而對於視網膜屏來說又有高清的體驗。

如果瀏覽器不支援srcset,則預設載入src裡面的圖片。

但是你會發現實際情況並不是如此,在Mac上的Chrome它會同時載入srcset裡面的那張2x的,還會再去載入src裡面的那張,載入兩張圖片。順序是先把所有srcset裡面的載入完了,再去載入src的。這個策略比較奇怪,它居然會載入兩張圖片,如果不寫src,則不會載入兩張,但是相容性就沒那麼好。這個可能是因為瀏覽器認為,既然有srcset就不用寫src了,如果寫了src,使用者可能是有用的。而使用picture就不會載入兩張

3.使用background-image
.banner{
  background-image: url(/static/large.jpg);
}

@media screen and (max-width: 767px){
  background-image: url(/static/small.jpg);
}
複製程式碼
4.使用picture標籤

picturefill.min.js :解決IE等瀏覽器不支援 的問題

<picture>
    <source srcset="banner_w1000.jpg" media="(min-width: 801px)">
    <source srcset="banner_w800.jpg" media="(max-width: 800px)">
    <img src="banner_w800.jpg" alt="">
</picture>

<!-- picturefill.min.js 解決IE等瀏覽器不支援 <picture> 的問題 -->
<script type="text/javascript" src="js/vendor/picturefill.min.js"></script>
複製程式碼

picture必須要寫img標籤,否則無法顯示,對picture的操作最後都是在img上面,例如onload事件是在img標籤觸發的,picturesource是不會進行layout的,它們的寬和高都是0。

另外使用source,還可以對圖片格式做一些相容處理:

<picture>
    <source type="image/webp" srcset="banner.webp">
    <img src="banner.jpg" alt="">
</picture>
複製程式碼

總結:響應式佈局的實現可以通過媒體查詢+px,媒體查詢+百分比,媒體查詢+rem+js,vm/vh,vm/vh +rem這幾種方式來實現。但每一種方式都是有缺點的,媒體查詢需要選取主流裝置寬度尺寸作為斷點針對性寫額外的樣式進行適配,但這樣做會比較麻煩,只能在選取的幾個主流裝置尺寸下呈現完美適配,另外使用者體驗也不友好,佈局在響應斷點範圍內的解析度下維持不變,而在響應斷點切換的瞬間,佈局帶來斷層式的切換變化,如同卡帶的唱機般“咔咔咔”地一下又一下。通過百分比來適配首先是計算麻煩,第二各個屬性中如果使用百分比,其相對的元素的屬性並不是唯一的,這樣就造成我們使用百分比單位容易使佈局問題變得複雜。通過採用rem單位的動態計算的彈性佈局,則是需要在頭部內嵌一段指令碼來進行監聽解析度的變化來動態改變根元素字型大小,使得CSSJS 耦合了在一起。通過利用純css視口單位實現適配的頁面,是既能解決響應式斷層問題,又能解決指令碼依賴的問題的,但是相容性還沒有完全能結構接受。

響應式佈局的成型方案

現在的css,UI框架等都已經考慮到了適配不同螢幕解析度的問題,實際專案中我們可以直接使用這些新特性和框架來實現響應式佈局。可以有以下選擇方案:

  • 利用上面的方法自己來實現,比如CSS3 Media Query,rem,vw等
  • Flex彈性佈局,相容性較差
  • Grid網格佈局,相容性較差
  • Columns柵格系統,往往需要依賴某個UI庫,如Bootstrap

響應式佈局的要點

在實際專案中,我們可能需要綜合上面的方案,比如用rem來做字型的適配,用srcset來做圖片的響應式,寬度可以用remflex,柵格系統等來實現響應式,然後可能還需要利用媒體查詢來作為響應式佈局的基礎,因此綜合上面的實現方案,專案中實現響應式佈局需要注意下面幾點:

  • 設定viewport
  • 媒體查詢
  • 字型的適配(字型單位)
  • 百分比佈局
  • 圖片的適配(圖片的響應式)
  • 結合flex,grid,BFC,柵格系統等已經成型的方案

參考文章:

  1. 響應式佈局的常用解決方案對比(媒體查詢、百分比、rem和vw/vh)
  2. 純CSS3使用vw和vh視口單位實現自適應
  3. 你真的瞭解響應式佈局嗎?
  4. 移動端H5頁面 iphone6的適配技巧
  5. 響應式開發心得
  6. 詳解前端響應式佈局、響應式圖片,與自制柵格系統
  7. 基於媒體查詢和 rem 的響應式佈局實踐
  8. 從網易與淘寶的font-size思考前端設計稿與工作流
  9. 移動端前端適配方案對比

相關文章