移動端適配方案

  • 移動端適配方案:
    1)viewport(scale=1/dpr)
    2)rem
    3)flex
    4)vm/vh
    一、什么是移動端適配
  • 移動端Web頁面,即常說的H5頁面、手機頁面、webview頁面;
  • 手機設備屏幕尺寸不一,做移動端的Web頁面,需要考慮安卓/IOS的各種尺寸設備上的兼容,針對移動端設備的頁面,設計與前端實現怎樣做能更好地適配不同屏幕寬度的移動設備;
  • 目的:在不同尺寸的手機設備上,頁面“相對性的達到合理的展示(自適應)”或者“保持統一效果的等比縮放(看起來差不多)”;

二、viewport視口

瀏覽器可見視口和布局視口

  • visual viewport:可見視口,即屏幕寬度;
  • layout viewport:布局視口,即DOM寬度;
  • idea viewport:理想適口,使布局視口就是可見視口;
  • 設備寬度(visual viewport)與DOM寬度(layout viewport),scale的關系是:
    (visual viewport)= (layout viewport)* scale
  • 獲取屏幕寬度(visual viewport)的尺寸:window. innerWidth/Height
  • 獲取DOM寬度(layout viewport)的尺寸:document. documentElement. clientWidth/Height
  • 完美適配:首先不需要用戶縮放和橫向滾動條就能正常的查看網站的所有內容;第二,顯示的文字的大小合適,比如一段14px大小的文字,不會因為在一個高密度像素的屏幕里顯示得太小而無法看清,理想的情況是這段14px的文字無論是在何種密度屏幕,何種分辨率下,顯示出來的大小都是差不多的。當然,不只是文字,其他元素像圖片什么的也是這個道理,ppk把這個viewport叫做 ideal viewport,也就是第三個viewport——移動設備的理想viewport;
  • 移動設備默認的viewport是layout viewport,也就是那個比屏幕要寬的viewport,但在進行移動設備網站的開發時,我們需要的是ideal viewport。那么怎么才能得到ideal viewport呢?這就該輪到meta標簽出場了;meta標簽的作用是讓當前viewport的寬度等于設備的寬度,同時不允許用戶手動縮放。也許允不允許用戶縮放不同的網站有不同的要求,但讓viewport的寬度等于設備的寬度,這個應該是大家都想要的效果,如果你不這樣的設定的話,那就會使用那個比屏幕寬的默認viewport,也就是說會出現橫向滾動條;
    這個name為viewport的meta標簽到底有哪些東西呢,又都有什么作用呢?

meta viewport 標簽首先是由蘋果公司在其safari瀏覽器中引入的,目的就是解決移動設備的viewport問題。后來安卓以及各大瀏覽器廠商也都紛紛效仿,引入對meta viewport的支持,事實也證明這個東西還是非常有用的。

在蘋果的規范中,meta viewport 有6個屬性(暫且把content中的那些東西稱為一個個屬性和值),如下:
width:設置layout viewport 的寬度,為一個正整數,或字符串"width-device";
initial-scale:設置頁面的初始縮放值,為一個數字,可以帶小數;
minimum-scale:允許用戶的最小縮放值,為一個數字,可以帶小數;
maximum-scale:允許用戶的最大縮放值,為一個數字,可以帶小數;
height:設置layout viewport 的高度,這個屬性對我們并不重要,很少使用;
user-scalable:是否允許用戶進行縮放,值為"no"或"yes", no 代表不允許,yes代表允許;

  • 設置理想視口:把默認的layout viewport的寬度設為移動設備的屏幕寬度,得到理想視口(ideal viewport):
<meta name="viewport" content="width=device-width,initial-scale=1">

參考:移動前端開發之viewport的深入理解

三、物理像素(physical pixel)

  • 物理像素又被稱為設備像素,他是顯示設備中一個最微小的物理部件,每個像素可以根據操作系統設置自己的顏色和亮度,所謂的一倍屏、二倍屏(Retina)、三倍屏,指的是設備以多少物理像素來顯示一個CSS像素,也就是說,多倍屏以更多更精細的物理像素點來顯示一個CSS像素點,在普通屏幕下1個CSS像素對應1個物理像素,而在Retina屏幕下,1個CSS像素對應的卻是4個物理像素,關于這個概念,看一張“田”字示意圖就會清晰了;

四、CSS像素

  • CSS像素是一個抽象的單位,主要使用在瀏覽器上,用來精確度量WEB頁面上的內容,一般情況下,CSS像素稱為與設備無關的像素(device-independent pixel),簡稱DIPs,CSS像素顧名思義就是我們寫CSS時所用的像素;

作為Web開發者,我們接觸的更多的是用于控制元素樣式的樣式單位像素。這里的像素我們稱之為CSS像素
參考:CSS像素、物理像素、邏輯像素、設備像素比、PPI、Viewport #21
五、設備像素比

  • 設備像素比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)
  • 安卓設備根據屏幕像素密度可分為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像素與設備像素“田字圖解”

九、前端實現相關方式

  • viewport 設置理想視口
// 設置理想視口,使得DOM寬度(layout viewport)與屏幕寬度(visual viewport)一樣大,DOM文檔主寬度即為屏幕寬度,1個CSS像素(1px)由多少設備像素顯示由具體設備而不同;
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">

十、動態設置視口縮放為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的時候,同時在根元素上加上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;
  • 根據這個特點,可以根據設備寬度動態設置根元素的font-size,使得以rem為單位的元素在不同終端上以相對一致的視覺效果呈現;
  • 選取一個設備寬度作為基準,設置其根元素大小,其他設備根據此比例計算其根元素大小,比如使得iphone6根元素font-size=16px;
設備 設備大小 根元素font-size/px 寬度/rem
iPhone5/SE 320*568 js計算所得 --
iPhone6/7/8 375*667 16 23.4375
i6/7/8 Plus 414*736 js計算所得 --
iPhone x 375*812 js計算所得 --
iPad 768*1024 js計算所得 --
- 360 js計算所得 --
  • 根元素fontSize公式:width/fontSize = baseWidth/baseFontSize;
    其中,baseWidth, baseFontSize是選為基準的設備寬度及其根元素大小,width, fontSize為所求設備的寬度及其根元素大小;

十四、動態設置根元素fontSize

// 以320px的屏幕為基準
const oHtml = document.getElementsByTagName('html')[0]
const width = oHtml.clientWidth;
// 320px的屏幕基準像素為12px
oHtml.style.fontSize = 12 * (width / 320) + "px";
/**
  * 以下這段代碼是用于根據移動端設備的屏幕分辨率計算出合適的根元素的大小
  * 當設備寬度為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,寫樣式時不必一直手動計算;
/* 
 * 此處 $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之前;

十七、vm/vh:CSS單位

  • vw(view-width), vh(view-height) 這兩個單位是CSS新增的單位,表示視區寬度/高度,視區總寬度為100vw, 總高度為100vh;
  • 視區指瀏覽器內部的可視區域大小:window.innerWidth/Height;

十八、一些問題

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

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

十九、手淘的實現方案

通過一段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;
  • 切圖使用三倍精度圖,以適應三倍屏(這個目前我還沒有實際應用過)

二十二、Retina視網膜屏
???????Retina視網膜屏,是指人眼在正常觀察距離(iPhone定義為10英寸,iPad定義為15英寸)下,視網膜已經無法分辨單個像素,不再出現像素顆粒感,僅能觀察到如絲般細膩的畫面,可以理解為超高分辨率屏幕;

  • 這是一種顯示技術,可以將把更多的像素點壓縮至一塊屏幕里,從而達到更高的分辨率并提高屏幕顯示的細膩程度,這種分辨率在正常觀看距離下足以使人肉眼無法分辨其中的單獨像素。

最先使用retina屏幕是iphone 4,屏幕分辨率為960 * 640(326ppi)。

對比如下兩幅圖,可以清晰地看出是否 Retina 屏的顯示差異:


屏幕快照 2018-08-20 下午3.00.23.png

兩代iPhone 的物理尺寸(屏幕寬高有多少英寸)是一樣的,從上圖可以看出,iphone 4的顯示效果要明顯好于iphone 3GS,雖然 iPhone 4 分辨率提高了,但它不同于普通的電腦顯示器那樣為了顯示更多的內容,而是提升顯示相同內容時的畫面精細程度。這種提升方式是靠提升單位面積屏幕的像素數量,即像素密度來提升分辨率,這樣做的主要目的是為了提高屏幕顯示畫面的精細程度。以第三代 MacBook Pro with Retina Display為例, 工作時顯卡渲染出的2880x1880個像素每四個一組,輸出原來屏幕的一個像素顯示的大小區域內的圖像。這樣一來,用戶所看到的圖標與文字的大小與原來的1440x900分辨率顯示屏相同,但精細度是原來的4倍。

注意:在桌面顯示器中,我們調整了顯示分辨率,比如從 800 * 600 調整到 1024 * 768 時,屏幕的文字圖標會變小,顯示的內容更多了。但 Retina 顯示方式不會產生這樣的問題,或者說, Retina 顯示技術解決的是顯示畫面精細程度的問題,而不是解決顯示內容容量的問題。

二十三、相關鏈接

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • h5的移動端適配核心的問題有兩個: 1.因為目前手機品牌眾多,手機的寬度不統一,所以第一個問題就是如何讓不同的手機...
    國服閱讀 3,359評論 0 2
  • 剛開始做移動端web開發的同學應該都碰到過頁面適配問題,為什么我在開發手機上調試好的頁面在其他手機會有這樣或那樣的...
    留七七閱讀 19,529評論 5 80
  • 適配:顧名思義,在不同尺寸的手機設備上,頁面相對性的達到合理的展示(自適應)或者“保持統一效果的等比縮放” 適配應...
    Imflyer閱讀 590評論 0 0
  • html5 AD:百G視頻免費分享 移動端越來越被大眾所接收,那么相應的技術就越來越向它靠攏,這是一種不可阻擋的趨...
    前端啊啊閱讀 311評論 0 0
  • 方案一: 最新方案:(隆重推薦) 1、下載MateHandler.js,并引入頁面2、head里加入 3、設置bo...
    晨光2016閱讀 920評論 0 0