移動端Web頁面適配方案

更新:完整js代碼和sass mixin已上傳於gitHub,點擊此處可獲取css

===================================================html

移動端Web頁面,即常說的H5頁面、手機頁面、webview頁面等。前端

手機設備屏幕尺寸不一,作移動端的Web頁面,須要考慮在安卓/IOS的各類尺寸設備上的兼容,這裏總結的是針對移動端設備的頁面,設計與前端實現怎樣作能更好地適配不一樣屏幕寬度的移動設備。android

適配的目標

引用一文章的描述:git

在不一樣尺寸的手機設備上,頁面「相對性的達到合理的展現(自適應)」或者「保持統一效果的等比縮放(看起來差很少)」。

概念理解

在作適配以前,須要先理解一些概念。對於不理解的地方,能夠搜索更多文章看看,本文總結的也是摘抄了其餘文章的描述,本文末有附相關連接。github

viewport視口

viewport是嚴格的等於瀏覽器的窗口。viewport與跟viewport有關的meta標籤的關係,詳細建議讀一讀這篇文章:移動前端開發之viewport的深刻理解viewport與佈局的關係,能夠看下這篇文章:在移動瀏覽器中使用viewport元標籤控制佈局web

visual viewport 可見視口 屏幕寬度
layout viewport 佈局視口 DOM寬度
ideal viewport 理想適口:使佈局視口就是可見視口
設備寬度(visual viewport)與DOM寬度(layout viewport), scale的關係爲:瀏覽器

  • (visual viewport)= (layout viewport)* scale

獲取屏幕寬度(visual viewport)的尺寸:window. innerWidth/Height
獲取DOM寬度(layout viewport)的尺寸:document. documentElement. clientWidth/Heightsass

設置理想視口:把默認的layout viewport的寬度設爲移動設備的屏幕寬度,獲得理想視口(ideal viewport):app

<meta name="viewport" content="width=device-width,initial-scale=1">

物理像素(physical pixel)

物理像素又被稱爲設備像素,他是顯示設備中一個最微小的物理部件。每一個像素能夠根據操做系統設置本身的顏色和亮度。所謂的一倍屏、二倍屏(Retina)、三倍屏,指的是設備以多少物理像素來顯示一個CSS像素,也就是說,多倍屏以更多更精細的物理像素點來顯示一個CSS像素點,在普通屏幕下1個CSS像素對應1個物理像素,而在Retina屏幕下,1個CSS像素對應的倒是4個物理像素。關於這個概念,看一張"田"字示意圖就會清晰了。

CSS像素

CSS像素是一個抽像的單位,主要使用在瀏覽器上,用來精確度量Web頁面上的內容。通常狀況之下,CSS像素稱爲與設備無關的像素(device-independent pixel),簡稱DIPs。CSS像素顧名思義就是咱們寫CSS時所用的像素。

設備像素比dpr(device pixel ratio)

設備像素比簡稱爲dpr,其定義了物理像素和設備獨立像素的對應關係。它的值能夠按下面的公式計算獲得:

設備像素比 = 物理像素 / 設備獨立像素

在Retina屏的iphone上,devicePixelRatio的值爲2,也就是說1個css像素至關於2個物理像素。一般所說的二倍屏(retina)的dpr是2, 三倍屏是3。

viewport中的scale和dpr是倒數關係。
獲取當前設備的dpr:

  • JavaScript: window.devicePixelRatio
  • CSS: -webkit-device-pixel-ratio, -webkit-min-device-pixel-ratio, -webkit-max-device-pixel-ratio。不一樣dpr的設備,可根據此作一些樣式適配(這裏只針對webkit內核的瀏覽器和webview)。

設備獨立像素dip與設備像素dp

dip(device independent pixels,設備獨立像素)與屏幕密度有關。dip能夠用來輔助區分視網膜設備仍是非視網膜設備。
dp(device pixels, 設備像素)。

謝謝@游魚與魚指出:dp與dip是有區別的

安卓設備根據屏幕像素密度可分爲ldpi、mdpi、hdpi、xhdpi等不一樣的等級。規定以160dpi爲基準,1dp=1px。若是密度是320dpi,則1dp=2px,以此類推。
IOS設備:從IPhone4開始爲Retina屏

  • CSS像素與設備獨立像素之間的關係依賴於當前的縮放等級。

屏幕像素密度PPI(pixel per inch)

屏幕像素密度是指一個設備表面上存在的像素數量,它一般以每英寸有多少像素來計算(PPI)。屏幕像素密度與屏幕尺寸和屏幕分辨率有關,在單一變化條件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。

屏幕密度 = 對角線分辨率/屏幕尺寸

概念關係圖

屏幕尺寸、屏幕分辨率-->對角線分辨率/屏幕尺寸-->屏幕像素密度PPI
                                             |
              設備像素比dpr = 物理像素 / 設備獨立像素dip(dp)
                                             |
                                       viewport: scale
                                             |
                                          CSS像素px

CSS像素與設備像素「田字圖解」
屏幕尺寸示意圖

前端實現相關方式

下面大體列下前端在實現適配上常採用的方式。百分比、em單位的使用就沒必要說了。

viewport

設置理想視口

<meta name="viewport" content="width=width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

設置理想視口,使得DOM寬度(layout viewport)與屏幕寬度(visual viewport)同樣大,DOM文檔主寬度即爲屏幕寬度。1個CSS像素(1px)由多少設備像素顯示由具體設備而不一樣。

動態設置視口縮放爲1/dpr

對於安卓,全部設備縮放設爲1,對於IOS,根據dpr不一樣,設置其縮放爲dpr倒數。設置頁面縮放可使得1個CSS像素(1px)由1個設備像素來顯示,從而提升顯示精度;所以,設置1/dpr的縮放視口,能夠畫出1px的邊框。

無論頁面中有沒有設置viewport,若無,則設置,如有,則改寫,設置其scale爲1/dpr。

(function (doc, win) {
  var docEl = win.document.documentElement;
  var resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize';
  var metaEl = doc.querySelector('meta[name="viewport"]');
  var dpr = 0;
  var scale = 0;

  // 對iOS設備進行dpr的判斷,對於Android系列,始終認爲其dpr爲1
  if (!dpr && !scale) {
    var isAndroid = win.navigator.appVersion.match(/android/gi);
    var isIPhone = win.navigator.appVersion.match(/[iphone|ipad]/gi);
    var devicePixelRatio = win.devicePixelRatio;

    if(isIPhone) {
      dpr = devicePixelRatio;
    } else {
      drp = 1;
    }
    
    scale = 1 / dpr;
  }

  /**
    * ================================================
    *   設置data-dpr和viewport
    × ================================================
    */

  docEl.setAttribute('data-dpr', dpr);
  // 動態改寫meta:viewport標籤
  if (!metaEl) {
    metaEl = doc.createElement('meta');
    metaEl.setAttribute('name', 'viewport');
    metaEl.setAttribute('content', 'width=device-width, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
    document.documentElement.firstElementChild.appendChild(metaEl);
  } else {
    metaEl.setAttribute('content', 'width=device-width, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
  }

})(document, window);

px單位的適配

設置動態縮放視口後,在iPhone6上,縮放爲0.5,即CSS像素2px最終顯示效果爲1px,而在scale=1的設備,CSS像素1px顯示效果爲1px,那麼,爲了達到顯示效果一致,以px爲單位的元素(好比字體大小),其樣式應有適配不一樣dpr的版本,所以,在動態設置viewport: scale的時候,同時在html根元素上加上data-dpr=[dpr]可是這種方式仍是不夠,若是dpr爲2,3以外的其餘數值,px就沒辦法適配到。所以我會選擇都用rem爲單位進行適配。
樣式示例:

.p {
    font-size: 14px;

  [data-dpr="2"] & {
    font-size: 14px * 2;
  }

  [data-dpr="3"] & {
    font-size: 14px * 3;
  }
}

爲寫樣式方便,能夠藉助sass的mixin寫代碼片斷:

// 適配dpr的字體大小
@mixin font-dpr($font-size){
  font-size: $font-size;

  [data-dpr="2"] & {
      font-size: $font-size * 2;
  }

  [data-dpr="3"] & {
      font-size: $font-size * 3;
  }
}
@mixin px-dpr($property, $px) {
  #{$property}: $px;

  [data-dpr="2"] & {
    #{$property}: $px * 2;
  }

  [data-dpr="3"] & {
    #{$property}: $px * 3;
  }
}

// 使用
@include font-dpr(14px);
@include px-dpr(width, 40px); @include px-dpr(height, 40px);

設置縮放視口與設置理想視口有什麼不一樣

問題:viewport設爲理想視口(scale=1),基本已經知足適配,爲何要動態設置viewport縮放?
緣由:iPhone6爲例,dpr爲2,縮放設爲0.5,則DOM寬度爲750,縮放後顯示恰好爲屏幕寬度375,而總的CSS像素實際上是750,與設備像素一致,這樣1px的CSS像素,佔用的物理像素也是1;而viewport設置縮放爲1的理想視口狀況下,DOM寬度爲375,顯示也恰好是屏幕寬度,然而1px的CSS像素,佔用的物理像素是2。這樣說來,這樣設置能夠實現1px的線條在二倍屏的顯示。由於: CSS像素與設備像素的關係依賴於屏幕縮放。
驗證:設備:iPhone6,
在scale=0.5時,1px邊框顯示效果;
在scale=1.0時,1px邊框顯示效果;
在scale=0.5時,2px邊框顯示效果;
經過對比後發現,在scale=0.5時,1px的線比scale=1.0要細,這也就解決了1px線條的顯示問題。

rem(一個CSS單位)

定義:font size of the root element.

這個單位的定義和em相似,不一樣的是em是相對於父元素,而rem是相對於根元素。rem定義是根元素的font-size, 以rem爲單位,其數值與px的關係,需相對於根元素<html>的font-size計算,好比,設置根元素font-size=16px, 則表示1rem=16px。關於rem更多的解讀,建議能夠閱讀本文末附的騰訊一團隊的文章《web app變革之rem》。

根據這個特色,能夠根據設備寬度動態設置根元素的font-size,使得以rem爲單位的元素在不一樣終端上以相對一致的視覺效果呈現。

選取一個設備寬度做爲基準,設置其根元素大小,其餘設備根據此比例計算其根元素大小。好比使得iPhone6根元素font-size=16px。

設 備 設備寬度 根元素font-size/px 寬度/rem
iPhone5 320 js計算所得 --
iPhone6 375 16 23.4375
i6 Plus 414 js計算所得 --
- 360 js計算所得 --
根元素fontSize公式: width/fontSize = baseWidth/baseFontSize

其中,baseWidth, baseFontSize是選爲基準的設備寬度及其根元素大小,width, fontSize爲所求設備的寬度及其根元素大小

動態設置根元素fontSize

/**
  * 如下這段代碼是用於根據移動端設備的屏幕分辨率計算出合適的根元素的大小
  * 當設備寬度爲375(iPhone6)時,根元素font-size=16px; 依次增大;
  * 限制當爲設備寬度大於768(iPad)以後,font-size再也不繼續增大
  * scale 爲meta viewport中的縮放大小
  */
(function (doc, win) {
  var docEl = win.document.documentElement;
  var resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize';
  /**
    * ================================================
    *   設置根元素font-size
    * 當設備寬度爲375(iPhone6)時,根元素font-size=16px; 
    × ================================================
    */
  var refreshRem = function () {
    var clientWidth = win.innerWidth
                      || doc.documentElement.clientWidth
                      || doc.body.clientWidth;

    console.log(clientWidth)
    if (!clientWidth) return;
    var fz;
    var width = clientWidth;
    fz = 16 * width / 375;
    docEl.style.fontSize = fz + 'px';
  };

  if (!doc.addEventListener) return;
  win.addEventListener(resizeEvt, refreshRem, false);
  doc.addEventListener('DOMContentLoaded', refreshRem, false);
  refreshRem();

})(document, window);

rem計算(px2rem)

對於須要使用rem來適配不一樣·屏幕的元素,使用rem來做爲CSS單位,爲了方便,能夠藉助sass寫一個函數計算px轉化爲rem, 寫樣式時沒必要一直手動計算。sass函數的使用若不熟悉可看下這篇文章:如何編寫自定義Sass 函數, 也可使用sass的mixin來寫,我的以爲用函數寫更適合。

/* 
 * 此處 $base-font-size 具體數值根據設計圖尺寸而定
 * flexible中設置的標準是【fontSize=16px, when 屏幕寬度=375】,所以,按此標準進行計算,
 * 若設計圖爲iPhone6(375*667)的二倍稿,則$base-font-size=32px
 *
 */ 
@function px2rem($px, $base-font-size: 32px) {
  @if (unitless($px)) {
    @warn "Assuming #{$px} to be in pixels, attempting to convert it into pixels for you";
    @return px2rem($px + 0px); // That may fail.
  } @else if (unit($px) == rem) {
    @return $px;
  }
  @return ($px / $base-font-size) * 1rem;
}

// 使用,eg:
font-size: px2rem(18px);

問題思考

我以前一直在想一個問題,選取哪一個設備來作基準、屏幕寬度等分爲多少比較合適,設計圖給多大寬度的版本?被選取做爲基準的設備,應當就是前端須要設計提供的設計圖版本,這樣能夠避免一些尺寸上的糾纏,而等分爲多少等分,除了考慮方便設計,是否須要考慮其餘問題?對於根元素font-size沒有手動設置的狀況,1rem究竟等於多少?

瞭解到的一些事實:

  • 某些Android設備會丟掉 rem 小數部分(具體是哪些設備,搜到的文章中沒有說),那麼1rem對應的px少些,在這些安卓設備上顯示偏差就會較小,固然若是不存在會丟掉小數這個問題,這一說也就沒必要考慮了。
  • 未設置font-size狀況下,1rem的大小具體看瀏覽器的實現,默認的根元素大小是font-size=16px
  • 目前通常會選取iPhone6做爲基準,設計圖便要iPhone6的二倍圖
  • 當動態縮放視口爲1/dpr, 計算所得的根元素fontSize也會跟着縮放,即若理想視口(scale=1), iPhone6根元素fontSize=16px; 若scale=0.5, iPhone6根元素fontSize=32px; 所以設置視口縮放應放於設置根元素fontSize以前。

flex佈局

flex佈局對於屏幕適配也頗有幫助,有些地方經過flex佈局的實現方式,效果會比較合理。
關於flex佈局,暫時不瞭解的建議閱讀阮一峯老師的教程,分語法和實踐兩篇,講得很清晰易懂實用。
Flex佈局教程:語法篇

vm/vh:CSS單位

vw(view-width), vh(view-height) 這兩個單位是CSS新增的單位,表示視區寬度/高度,視區總寬度爲100vw, 總高度爲100vh。

視區指瀏覽器內部的可視區域大小: window.innerWidth/Height

一些問題

upsampling/downsampling

DownSampling: 大圖放入比圖片尺寸小的容器中時,出現像素分割成就近色

不一樣scale顯示同一圖片基本無問題;
同一sacle,不一樣倍數圖,存在色差(Downsampling)

關於這個我還不是很瞭解,暫時記一下。

手淘的實現方案

下面主要根據個人理解摘錄了手淘公開的實現方案,詳細能夠去gitHub搜索查看,文末也附了連接。
圖解設計與前端協做方案:圖:手機淘寶團隊適配協做模式
[淘寶手淘團隊h5頁面終端適配開源庫:lib-flexible]()

方案關鍵點:

  • 動態改寫<meta name="viewport">標籤
  • 給<html>元素添加data-dpr屬性,而且動態改寫data-dpr的值
  • 給<html>元素添加font-size屬性,而且動態改寫font-size的值

經過一段JS代碼根據設備的屏幕寬度、dpr設置根元素的data-dpr和font-size, 這段JS代碼要在全部資源加載以前執行,建議作內聯處理。

各類元素(文本、圖片)處理方案參考:
圖:怎樣讓你的網站適應視網膜分辨率

px轉rem的mixin

// 使用sass的混合宏
// 淘寶手淘的方案裏,i6(375pt)屏幕寬度爲10rem,即font-size=75px, scale=0.5 因設計圖爲二倍圖,$base-font-size=75px
@mixin px2rem($property,$px-values,$baseline-px:16px,$support-for-ie:false){
    //Conver the baseline into rems
    $baseline-rem: $baseline-px / 1rem * 1;
    //Print the first line in pixel values
    @if $support-for-ie {
        #{$property}: $px-values;
    }
    //if there is only one (numeric) value, return the property/value line for it.
    @if type-of($px-values) == "number"{
        #{$property}: $px-values / $baseline-rem;
    }
    @else {
        //Create an empty list that we can dump values into
        $rem-values:();
        @each $value in $px-values{
            // If the value is zero or not a number, return it
            @if $value == 0 or type-of($value) != "number"{
                $rem-values: append($rem-values, $value / $baseline-rem);
            }
        }
        // Return the property and its list of converted values
        #{$property}: $rem-values;
    }
}

小結

  • 適配不一樣屏幕寬度以及不一樣dpr,經過動態設置viewport(scale=1/dpr) + 根元素fontSize + rem, 輔助使用vw/vh等來達到適合的顯示;
  • 若無需適配可顯示1px線條,也能夠不動態設置scale,只使用動態設置根元素fontSize + rem + 理想視口;
  • 當視口縮放,計算所得的根元素fontSize也會跟着縮放,即若理想視口(scale=1), iPhone6根元素fontSize=16px; 若scale=0.5, iPhone6根元素fontSize=32px; 所以沒必要擔憂rem的計算;
  • !!css單位:之前我認爲這樣比較好:適配元素rem爲單位,正文字體及邊距宜用px爲單位;如今認爲所有用rem便可,包括字體大小,不用px
  • px爲單位的元素,需根據dpr有不一樣的大小,如大小12px, dpr=2則採用24px, 使用sass mixin簡化寫法;
  • 配合scss函數,簡化px2rem轉換,且易於維護(若需修改$base-font-size, 無需手動從新計算全部rem單位);
  • px2rem函數的$base-font-size只跟根元素fontSize的基準(此文中是【fontSize=16px when 375】)以及設計圖的大小有關,按此基準,若設計圖爲iPhone6二倍稿,則$base-font-size=32px,參數傳值直接爲設計圖標註尺寸;
  • 使用iPhone6(375pt)二倍設計圖:寬度750px;
  • 切圖使用三倍精度圖,以適應三倍屏(這個目前我尚未實際應用過)

相關連接:

相關文章
相關標籤/搜索