移動端適配總結

前言: 我的總結, 結合各大神的文章, 若有不妥之處, 請指出~css

前置概念

設備像素(Device Pixels)

設備屏幕的物理像素,表示屏幕上能夠鋪多少個點點,而不是一個絕對長度單位(例如in,mm); 單位是px,好比iPhone6的 (750 x 1334px)html

分辨率(Resolution)

一個物理概念
對於屏幕,分辨率通常表示屏幕上顯示的物理像素總和。好比,咱們說iPhone6屏幕分辨率是(750 x 1334px)
對於圖像,概念等同於圖像尺寸、圖像大小、像素尺寸等等。好比,咱們說(20 x 20px)的iconandroid

CSS像素(CSS Pixels)

是Web編程的概念,指的是CSS樣式代碼中使用的邏輯像素, 或者稱爲設備獨立像素, 由於只與設備相關, 例如: iPhone6 375 x 667px
1個CSS像素在不一樣設備上可能對應不一樣的物理像素數,這個比值是設備的屬性(Device Pixel Ratio,設備像素比)ios

經過 document.documentElement.clientWidth/clientHeight / document.documentElement.getBoundingClientRect().width 獲取css3

在CSS規範中,長度單位能夠分爲絕對單位和相對單位。px是一個相對單位,相對的是設備像素(Device Pixels)git

設備獨立像素(device-independent pixels (DIP) / Density-independent Pixels (DP))

Android設備的特色是屏幕尺寸不少,所以爲了顯示能儘可能和設備無關,提出了dip,參照的density是160。github

// 當屏幕密度density爲160(單位是ppi或者dpi,一個意思)時,px === dip
px = dip * density / 160 
// 因此
dip = px * 160 / dpi

注: 此處只針對於Android, windows 也有 DIP 概念, 含義不一樣, IOS貌似不存在編程

設備像素比 (Device Pixel Ratio (DPR))

Device pixel ratio, the ratio between physical pixels and logical pixels used by cascading style sheets (CSS): other names for it are 「CSS Pixel Ratio」 and 「dppx」
表示1個CSS像素(寬度)等於幾個物理像素(寬度)
DPR = 物理像素(設備像素) / 邏輯像素(css像素/設備獨立像素) // [未縮放]

像素密度

像素密度也叫顯示密度或者屏幕密度,縮寫爲DPI(Dots Per Inch)或者PPI(Pixel Per Inch)windows

// 屏幕對角線的像素尺寸 / 物理尺寸(inch 英寸)
Math.sqrt(750*750 + 1334*1334) / 4.7 = 326ppi

視口(viewport)

桌面上視口寬度等於瀏覽器寬度,但在手機上有所不一樣。瀏覽器

佈局視口(layout viewport)

手機上爲了容納爲桌面瀏覽器設計的網站,默認佈局視口寬度遠大於屏幕寬度,爲了讓用戶看到網站全貌,它會縮小網站
document.documentElement.clientWidth

視覺視口(Visual viewport)

屏幕的可視區域,即物理像素尺寸, 可變, 與當前縮放值和設備的屏幕寬度有關
visual viewport寬度 = ideal viewport寬度 / 當前縮放值
能夠經過window.innerWidth 來獲取,但在Android 2, Oprea mini 和 UC 8中沒法正確獲取。

理想視口(ideal viewport)

ideal viewport是最適合移動設備的viewport,ideal viewport的寬度等於移動設備的屏幕寬度
在移動開發時, 在meta[name='viewport']中, 經過width = device-width把當前的viewport寬度設置爲理想視口, 不然寬度將默認爲佈局視口
ideal viewport並無一個固定的尺寸,不一樣的設備擁有有不一樣的ideal viewport。早期全部iPhone理想視口爲320x480px

因此,在沒有縮放的狀況下,屏幕的CSS像素寬度實際上是指理想視口的寬度,而meta標籤:

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

上面的meta指定了佈局視口=理想視口,而且禁止縮放。因此添上width=device-width的viewport meta後頁面變大了(一開始頁面內容小得看不清),其實是佈局視口變小了
initial-scale=1 解決了 iphone、ipad 不管橫豎屏都把寬度設爲豎屏時 ideal viewport 的寬度
width=device-width 解決了IE 不管橫豎屏都把寬度設爲豎屏時 ideal viewport 的寬度

visual viewport寬度 vs ideal viewport寬度

visual viewport寬度 = ideal viewport寬度 / 當前縮放值
當前縮放值 = ideal viewport寬度 / visual viewport寬度

參考:
http://www.ayqy.net/blog/%e5%...
https://github.com/jawil/blog...

mate 中的 device-width / width:

  • device-width 是設備實際的寬度,只和設備的分辨率有關,通常是設備物理像素/設備像素比,且不會隨着手機旋轉而改變其值, 所以並不適合開發響應式網站 (css邏輯像素)
  • width 指的是可視區域的寬度,佈局視口, 會和 viewport 的 scale 屬性相關,爲頁面的可視區域的寬度

能夠設置 <meta name="viewport" content="width=device-width"/>

適配方案

媒體查詢

最多見的方式,經過屏幕寬度(用CSS像素描述的寬度)來區分各類設備
前提:

<meta name="viewport" content="width=device-width"/>
// 響應式網站
@media (min-width:320px) { /* smartphones, portrait iPhone, portrait 480x320 phones (Android) */ }
@media (min-width:480px) { /* smartphones, Android phones, landscape iPhone */ }
@media (min-width:600px) { /* portrait tablets, portrait iPad, e-readers (Nook/Kindle), landscape 800x480 phones (Android) */ }
@media (min-width:801px) { /* tablet, landscape iPad, lo-res laptops ands desktops */ }
@media (min-width:1025px) { /* big landscape tablets, laptops, and desktops */ }
@media (min-width:1281px) { /* hi-res laptops and desktops */ }

/* 另外 */
min-width: 480px: Will target mobile devices in landscape mode and up
// 移動端
@media screen and (min-width: 320px) {
    html {
        font-size: 50px;
    }
}
@media screen and (min-width: 360px) {
    html {
        font-size: 56px;
    }
}
@media screen and (min-width: 414px) {
    html {
        font-size: 63px;
    }
}

// 或者, 根據具體需求肯定
@media screen and (max-width: 320px) {
  font-size: 32px;
}
@media screen and (min-width: 540px) {
  font-size: 54px;
}

缺點

難以覆蓋全部的尺寸, 寫起來比較繁瑣

優勢

兼容性良好

rem佈局

rem: 根元素(html)的字體大小. 即 1rem = html中設置的 font-size

獲取設備寬度
document.documentElement.getBoundingClientRect().width / document.documentElement.clientWidth

使用

原理: 比例問題, 根據設計圖比例計算出固定 rem 值
實現: 主要經過修改 html 的 fontSize
實現一 直接引入 參考

(function (doc, win) {
        var docEl = doc.documentElement,
            resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
            recalc = function () {
                var clientWidth = docEl.clientWidth;
                if (!clientWidth) return;
                if(clientWidth>=640){
                    docEl.style.fontSize = '100px';
                }else{
                    docEl.style.fontSize = 100 * (clientWidth / 640) + 'px'; // 640: 可根據設計圖來定
                }
            };

        if (!doc.addEventListener) return;
        win.addEventListener(resizeEvt, recalc, false);
        doc.addEventListener('DOMContentLoaded', recalc, false);
    })(document, window);

實現二 使用flexible.js
flexible,js
lib-flexible 手淘rem方案
移動端適配與dpr無關, 與dpr關聯是爲了處理1px兼容問題, 具體參考: 真的,移動端尺寸自適應與dpr無關, 從網易適配方案和下面手淘最新版適配方案也能夠看出

1, 舊版本
舊版本#17 代碼

處理過程以下:

1.先取 dpr (dpr = window.devicePixelRatio)
2.而後設置 scale = 1/dpr
3.而後就有 innerWidth 了, innerWidth = 375 / scale = 750px (device-width = document.documentElement.clientWidth)
4.而後將 innerWidth 分紅 10rem, font-size = innerWidth / 10 = 75px
;(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 = {});
    
    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));    
            }
        }
    }

    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;
    }

    docEl.setAttribute('data-dpr', dpr);
    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);
        }
    }

    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;
    }

    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);

    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'] = {}));

2, 新版本
新版本 2.0代碼

meta 標籤固定爲
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no」>

代碼邏輯爲:
取 width 分爲 10rem, font-size = width / 10

注: 手機淘寶首頁 2019-01-16, 也是這個方案, 可是是 width = 3.75rem, font-size = 100px, 估計是爲了方便除

代碼,添加部分註釋

(function flexible (window, document) {
  var docEl = document.documentElement
  var dpr = window.devicePixelRatio || 1

  // adjust body font size
  function setBodyFontSize () {
    if (document.body) {
      document.body.style.fontSize = (12 * dpr) + 'px' 
    }
    else {
      document.addEventListener('DOMContentLoaded', setBodyFontSize)
    }
  }
  setBodyFontSize();

  // set 1rem = viewWidth / 3.6
  function setRemUnit () {
    // var rem = docEl.clientWidth / 10 // 原始代碼
    var rem = docEl.clientWidth / 3.6  // 修改後代碼
    docEl.style.fontSize = rem + 'px'
  }

  setRemUnit()

  // reset rem unit on page resize
  window.addEventListener('resize', setRemUnit)
  window.addEventListener('pageshow', function (e) {
    // 參考: https://juejin.im/post/5c807802f265da2de33f4e1f
    // 頁面從瀏覽器的緩存中讀取該屬性返回 ture, 優化方案 window.performance.navigation.type
    // if (e.persisted) {  // 原始代碼
    //   setRemUnit()
    // }
    if (e.persisted || (window.performance && window.performance.navigation.type === 2)) { // 修改後代碼
      setRemUnit()
    }
  })

  // detect 0.5px supports 檢測是否支持0.5px, 用於1px問題
  if (dpr >= 2) {
    var fakeBody = document.createElement('body')
    var testElement = document.createElement('div')
    testElement.style.border = '.5px solid transparent'
    fakeBody.appendChild(testElement)
    docEl.appendChild(fakeBody)
    if (testElement.offsetHeight === 1) {
      docEl.classList.add('hairlines')
    }
    docEl.removeChild(fakeBody)
  }
}(window, document))

缺點:

一、iframe 問題
二、富文本問題

  1. 高清方案
  2. 部分android機型不兼容

五、系統字體縮放時, 發生變化, 由於和根元素緊密聯繫 - 解決方案: 縮放還原, 具體以下:
1> https://juejin.im/post/59f678...
2> 基於 flexible.js 添加如下代碼: https://juejin.im/post/5b9cb9...

var root = window.document.documentElement;
var fontSize = parseFloat(root.style.fontSize);
var finalFontSize = parseFloat(window.getComputedStyle(root).getPropertyValue("font-size"));
if(finalFontSize !== fontSize) {
    root.style.fontSize = fontSize * fontSize / finalFontSize + "px";
}

注:
window.getComputedStyle(docEl).getPropertyValue('font-size') // html最終的font-size大小

getComputedStyle

Window.getComputedStyle()方法返回一個對象,該對象在應用活動樣式表並解析這些值可能包含的任何基本計算後報告元素的全部CSS屬性的值。 私有的CSS屬性值能夠經過對象提供的API或經過簡單地使用CSS屬性名稱進行索引來訪問。

getPropertyValue

此 CSSStyleDeclaration.getPropertyValue() 接口會返回一個 DOMString ,這個返回值將會包含預請求的CSS屬性信息。

vw/vh (css3新增屬性)

1vw: 可視窗口寬度1%
1vh: 可視窗口高度1%
vmin/vmax: vw和vh中最小/最大的那個

實現:

<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
設計圖尺寸:設計圖元素尺寸 = 100vw:Y // Y 表示css樣式中設計圖某元素的大小, 單位vw

缺點:
一、沒有最大/最小限制
二、兼容性: ios8/andorid4,4 以上

vw + rem

優勢:

  1. 實現簡單
    不依賴插件及第三方庫
    幾行css代碼 就能夠實現
  2. 開發方便, 方便換算[100vw === (設計稿 / 100) rem === document.documentElement.clientWidth]
  3. 不影響px使用,完美兼容第三方組件庫
  4. 不存在富文本和iframe等兼容問題

實現

(如下實現以[rem與vw關係: rem:vw = 10:1 或者 1rem = 10vw]爲依據)
前提: <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />

一、 html 的 font-size 等於 1/10 的視口寬度 (即: 1rem = 1 / 10 * 100vw => 等同於lib-flexible 中 document.documentElement.style.fontSize = clientWidth / 10) [此處取1/10, 由於在淘寶方案也是取這個值, 爲了更好計算能夠去其餘值]

// $vw_design: 設計圖尺寸
// $vw_fontsize: 設計圖尺寸 / 10  假設把設計圖分爲10份, 每份的大小(設計圖的1rem), 並以此爲基數
html {
  font-size: ($vw_fontsize / $vw_design) * 100vw; // 直接寫 10vw

  // 同時,經過Media Queries 限制根元素最大最小值
  @media screen and (max-width: 320px) {
    font-size: 32px;
  }
  @media screen and (min-width: 540px) {
    font-size: 54px;
  }
}

二、計算使用scss函數: 設計圖元素尺寸 / (設計圖尺寸 / 10) * 1rem

// $basesize: 設計圖元素尺寸
@function rem($basesize) {
  @return ($basesize / $vw_fontsize) * 1rem; 
}

//簡化
@function rem($basesize) {
  @return ($basesize / $vw_design) * 10rem; 
}

計算原理:

假設設計圖元素所佔視口大小爲 X, 單位爲: vw

設計圖元素尺寸 / 設計圖尺寸 = (Y)rem / 100vw 
=> (X)vw = (設計圖元素尺寸 / 設計圖尺寸) * 100vw 
// 轉化爲以rem單位的數值Y
=> (Y)rem = (X)vw / 10

參考: https://juejin.im/post/5c0357...

測試

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- <meta name="viewport" content="width=640, user-scalable=no"> -->
    <!-- <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" /> -->
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Test</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }
        html {
            /* font-size: 13.33vw; */
        }
        #box {
            width: 2.667rem;
            height: 2.667rem;
            /* width: 1rem;
            height: 1rem; */
            /* width: 13.33vw;
            height: 13.33vw; */
            background: #ccc;
            border: 1px solid #000;
        }
    </style>
  </head>
  <body>
  
    <script>
        (function () {
            var dpr = window.devicePixelRatio;
            var meta = document.createElement('meta');
            var scale = 1 / dpr;
            meta.setAttribute('name', 'viewport');
            meta.setAttribute('content', 'width=device-width, user-scalable=no, initial-scale=' + scale +
            ', maximum-scale=' + scale + ', minimum-scale=' + scale);
            document.getElementsByTagName('head')[0].appendChild(meta);
            // 動態設置的縮放大小會影響佈局視口的尺寸
            function resize() {
                var deviceWidth  = document.documentElement.clientWidth; // 不設置 meta[name='viewport'], 等於980px
                document.documentElement.style.fontSize = (deviceWidth / 10) +'px';
            }
            resize();
            window.onresize = resize;
        })()
        // dpr = 2
        // 設備邏輯像素 = 設備物理像素(固定) / (dpr * scale) = 750 / (2 * 0.5) = 750 // 視口放大一倍
        // 當前縮放比 = ideal viewport / visual viewport(設備邏輯像素) = 375 / 750 = 0.5 // 頁面縮小一倍
    </script>
    <div id="box">
    </div>
  </body>
</html>
相關文章
相關標籤/搜索