移動端適配方案

討論適配方案以前,先了解幾個移動端的概念。css

基礎概念

設備像素(device pixels):又稱爲物理像素,是顯示屏的最小物理單位。在操做系統的調度下,每個設備像素都有本身的顏色值和亮度值。html

設備獨立像素(device independent pixels):基於計算機控制的座標系統和抽象像素(虛擬像素),由底層系統的程序使用,轉換爲物理像素的應用。例如meta裏面設置width=device-width,這個device-width就是設備獨立像素。
clipboard.pngandroid

在chrome裏 看到的375x667 320x480等等都是設備獨立像素,其數值上等於CSS像素數組。ios

設備像素比(device pixel ratio):簡稱dpr,定義了設備像素和設備獨立像素的對應關係,它的值能夠按公式獲得:設備像素比 = 設備像素/設備獨立像素git

目前瞭解到有三種方法實現適配:github

  • 固定高度,寬度自適應
  • 利用rem佈局,viewport縮放
  • vw適配

固定高度,寬度自適應

垂直方向用定值,水平方向用百分比、定值、flex都行。這樣設置以後就能夠不用管手機屏幕的尺寸進行開發。該方法使用了完美視口:chrome

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

可是該方法有個缺點,因爲高度固定,會致使頁面產生橫向拉伸的視覺效果。數組

rem佈局,viewport縮放

這也是淘寶使用的方案,根據屏幕寬度設定 rem 值,須要適配的元素都使用 rem 爲單位,不須要適配的元素仍是使用 px 爲單位。字體也須要經過CSS hack使用px爲單位,由於rem可能會致使字體異形或模糊。瀏覽器

下面爲手淘源代碼解析:app

;(function(win, lib) {
    var doc = win.document;
    var docEl = doc.documentElement;
    var metaEl = doc.querySelector('meta[name="viewport"]');
    var flexibleEl = doc.querySelector('meta[name="flexible"]');
    var dpr = 0;
    var scale = 0;
    var tid;
    var flexible = lib.flexible || (lib.flexible = {});
    
    //是否已經設置了viewport的meta標籤,若已經設置則根據設置的initial-scale來設置scale和dpr;是否手動設置了dpr,若已經設置則根據設置的initial-dpr或者maximum-dpr來設置scale和dpr
    if (metaEl) {
        console.warn('將根據已有的meta標籤來設置縮放比例');
        var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
        if (match) {
            scale = parseFloat(match[1]);
            dpr = parseInt(1 / scale);
        }
    } else if (flexibleEl) {
        var content = flexibleEl.getAttribute('content');
        if (content) {
            var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
            var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
            if (initialDpr) {
                dpr = parseFloat(initialDpr[1]);
                scale = parseFloat((1 / dpr).toFixed(2));    
            }
            if (maximumDpr) {
                dpr = parseFloat(maximumDpr[1]);
                scale = parseFloat((1 / dpr).toFixed(2));    
            }
        }
    }
    
    //若沒有手動設置scale或者dpr,則把android的dpr設置爲1,ios按照設備的devicePixelRatio 設置。
    if (!dpr && !scale) {
        var isAndroid = win.navigator.appVersion.match(/android/gi);
        var isIPhone = win.navigator.appVersion.match(/iphone/gi);
        var devicePixelRatio = win.devicePixelRatio;
        if (isIPhone) { 
            // iOS下,對於2和3的屏,用2倍的方案,其他的用1倍方案
            if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {                
                dpr = 3;
            } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
                dpr = 2;
            } else {
                dpr = 1;
            }
        } else {
            // 其餘設備下,仍舊使用1倍的方案
            dpr = 1;
        }
        scale = 1 / dpr;
    }
    
    //html上設置了data-dpr自定義屬性
    docEl.setAttribute('data-dpr', dpr);
    
    //設置viewport
    if (!metaEl) {
        metaEl = doc.createElement('meta');
        metaEl.setAttribute('name', 'viewport');
        metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
        if (docEl.firstElementChild) {
            docEl.firstElementChild.appendChild(metaEl);
        } else {
            var wrap = doc.createElement('div');
            wrap.appendChild(metaEl);
            doc.write(wrap.innerHTML);
        }
    }
    
    //獲取設備的width,根據width計算出rem具體出,rem表明html的fontSize
    function refreshRem(){
        var width = docEl.getBoundingClientRect().width;
        if (width / dpr > 540) {
            width = 540 * dpr;
        }
        var rem = width / 10;
        docEl.style.fontSize = rem + 'px';
        flexible.rem = win.rem = rem;
    }
    // 監聽window的resize和pageshow事件,實現css樣式的重繪
    win.addEventListener('resize', function() {
        clearTimeout(tid);
        tid = setTimeout(refreshRem, 300);
    }, false);
    win.addEventListener('pageshow', function(e) {
        if (e.persisted) {
            clearTimeout(tid);
            tid = setTimeout(refreshRem, 300);
        }
    }, false);
    
    //判斷document對象是否處於complete狀態,若是完成狀態咱們給body一個font-size=12*dpr的值,不然咱們判斷dom加載方法來實現body中的font-size的設置。這個設置是爲了頁面中字體的大小,而html中的font-size是爲了設置頁面的height,width等屬性
    if (doc.readyState === 'complete') {
        doc.body.style.fontSize = 12 * dpr + 'px';
    } else {
        doc.addEventListener('DOMContentLoaded', function(e) {
            doc.body.style.fontSize = 12 * dpr + 'px';
        }, false);
    }
    

    refreshRem();

    flexible.dpr = win.dpr = dpr;
    flexible.refreshRem = refreshRem;
    flexible.rem2px = function(d) {
        var val = parseFloat(d) * this.rem;
        if (typeof d === 'string' && d.match(/rem$/)) {
            val += 'px';
        }
        return val;
    }
    flexible.px2rem = function(d) {
        var val = parseFloat(d) / this.rem;
        if (typeof d === 'string' && d.match(/px$/)) {
            val += 'rem';
        }
        return val;
    }

})(window, window['lib'] || (window['lib'] = {}));

總結一下,淘寶使用的方案其實就是作了三點:

  • 動態生成viewport
  • 屏幕寬度設置 rem的大小,即給<html>設置font-size
  • 根據設備像素比(window.devicePixelRatio)給<html>設置data-dpr(css hack用)

設置 rem
例如設計圖給咱們的尺寸是7501000的。某個容器在設計圖的寬度是150px225px,那咱們在css裏面
寬度:150px/750px/10=150px/75px=2rem;
高度爲:225px/75px=3rem;

總結一下,佈局的時候,各元素的css尺寸=設計稿標註尺寸/設計稿橫向分辨率/10。

設置viewport和data-dpr
這兩個設置都是適配高清屏幕手機的px單位。

縮放是動態的,不一樣設備下的px顯示同樣的長度。例如設備像素比是2,縮放爲0.5,data-dpr爲2;設備像素比是3,縮放爲0.3333,data-dpr爲3。

而data-dpr用法見下圖,字體大小最好經過下面方式設置。

[data-dpr="2"] {
  font-size: 26px;
}
[data-dpr="3"] {
  font-size: 39px;
}

vw適配

現在vw已經獲得了衆多瀏覽器的支持,所以咱們能夠直接考慮將vw單位運用於咱們的適配佈局中。
vw是基於Viewport視窗(window.innerWidth/window.innerHeight)的長度單位。
對於viewport概念,可參考文章https://www.w3cplus.com/css/v...

vw、vh與視窗的寬高度關係以下:
1vw等於window.innerWidth的1%
1vh等於window.innerHeight的1%

所以,假設以750px的設計稿爲例,100vw = 750px,即1vw = 7.5px。那麼咱們能夠根據設計圖上的px值直接轉換成對應的vw值。咱們可使用PostCSS的插件postcss-px-to-viewport,讓咱們能夠直接在代碼中寫px。咱們能夠在一下情境下使用vw

  • 容器適配,可使用vw
  • 文本的適配,可使用vw
  • 大於1px的邊框、圓角、陰影均可以使用vw
  • 內距和外距,可使用vw
相關文章
相關標籤/搜索