css 手機適配


手淘H5移動端適配方案flexible源碼分析

 

移動端適配一直是一個值得探討的問題,在業余時間我找了一些頁面,查看了一些廠商對於移動端H5頁面的適配方案,看到了幾個典型的例子,今天就來記錄一下我看到的第一個典型的例子,也是我們公司目前普通H5項目正在使用的適配方案。

這個適配方案是lib-flexible,在看這個源碼的同時,我想先來回顧一下幾個概念:

1.  viewport

 在移動設備上,viewport是設備屏幕用來顯示我們網頁的那一塊區域,或者說是瀏覽器(或者Hybird App內的webview)用來展示我們網頁的那部分區域,viewport不局限於瀏覽器可視區域的大小,可能比瀏覽器的可視區域大,也可能比瀏覽器的可視區域小。viewport是一個與html中mate標簽相關的概念,如下:

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

上面這行代碼的作用是讓當前viewport的寬度等於設備寬度,頁面初始縮放比例為1,viewport最大的縮放比例為1,viewport最小的縮放比例也為1,同時不允許用戶拖動縮放。

屬性
作用

值類型

width
 規定頁面的寬度

可以為字符串值"device-width",或者正整數

initial-scale
 規定頁面的初始縮放比例 為數字,可以為小數
maximum-scale
 規定頁面的最大縮放比例 為數字,可以為小數
minimum-scale
 規定頁面的最小縮放比例 為數字,可以為小數
user-scalable
 規定是否允許用戶進行拖動縮放 yes或no,yes是允許,no則不允許

好了,先熟悉到這里,后面如果想對viewport有更深入透徹的研究,可以查看PPK大神的關於viewport的三篇文章。

2.設備像素比

 關於設備像素比,我們先賣個關子,后面會說。我們先來看一下另一個值得思考的問題,我們CSS中常用的單位px到底和我們移動設備屏幕上的像素(pixel)是什么關系?CSS里的1px等於移動設備屏幕上的1像素嗎?

 首先,我來繞一波,px確實是英文像素(pixel)的縮寫;但是!!!我們這里為了將CSS中的px和設備中的物理像素加以區分,CSS中的單位描述我們就用熟悉的px,設備的物理像素,我們則用pixel來加以區分!!!

 那么問題來了,我CSS中的1px到底等於設備物理像素1pixel嗎?----答案是:不一定!!!

 那么為什么是不一定呢?這里我們又要了解兩個相關概念:

  (1)物理像素:設備的物理像素,顧名思義就是一個移動設備在出廠時就固定了的像素,整個屏幕是由一個挨着一個間隙極小的像素組成的,是屏幕顯示中的基本單元,例如某款手機屏幕分辨率:1920*1080像素,這里所說的1920就是該款手機屏幕縱向的像素排布數量,1080就是橫向像素排布數量,這里的像素就是我們所說的物理像素pixel。

  (2)獨立像素:獨立像素也可以稱之為邏輯像素,一個邏輯像素是屏幕接受程序控制的最小單位,簡言之我們可以將這里的邏輯像素和我們CSS中的px建立起聯系,即CSS中的1px可以控制1個邏輯像素的顯示。

 書接前文,前面提到我們CSS中的1px不一定等於我們設備的物理像素1pixel,那么什么情況下等於?什么情況下又不等於?

  等於的情況:早在移動端視網膜屏幕上市以前,絕大部分手機的物理像素和邏輯像素其實是對等的,比如iphone 3 的手機屏幕(物理像素:320x480;邏輯像素:320x480)。這里就是CSS 中的1px等於移動設備的物理像素1pixel。也就是說此時,物理像素÷邏輯像素=1,這個比值就是設備像素比(dpr)。

  不等於的情況:當 iphone 4 手機問世時,掀起了視網膜平屏幕的浪潮,以iphone 4 手機屏幕為例(物理像素:640x960;邏輯像素:320x480),由此可見iphone 4  相比於iphone 3 的手機屏幕,物理像素多了一倍,但是邏輯像素卻沒有變化,那么iphone 4 的設備像素比: 物理像素÷邏輯像素=2,也就是說  dpr=2 。當然,隨着手機日新月異的發展,dpr=3的情況也是有的,例如總結的下表各主要手機型號的設備像素比:

手機型號 物理像素 獨立像素(邏輯像素) dpr 倍圖
iphone  5/5S/5E  640*1136  320*568  2  @2x
 iphone 6/7/8  750*1334  375*667  2  @2x
 iphone 6p/7p/8p  1242*2208  414*736  3  @3x

 安卓手機由於廠商眾多,並且型號尺寸眾多,現僅概括幾個常見比例供參考(不再列舉詳細的手機型號),重要的是理解原理:

手機型號 物理像素 邏輯像素 dpr 倍圖
Android 1 320*480 320*480 1 @1x
Android 2 540*960 360*640 1.5 @1.5x
Android 3 640*960 320*480 2 @2x
Android 4 720*1280 360*640 2 @2x
Android 5 1080*1920 360*640 3 @3x

好了,巴拉了這么多,該切入正題上lib-flexible源碼了,如下:

復制代碼
;(function(win, lib) {
    var doc = win.document;
    var docEl = doc.documentElement;
    var metaEl = doc.querySelector('meta[name="viewport"]');  // 獲取名為viewport的mate標簽
    var flexibleEl = doc.querySelector('meta[name="flexible"]'); // 獲取名為flexible的mate標簽
    var dpr = 0; // dpr (設備像素比)初始化置為0
    var scale = 0; // scale (縮放比例)
    var tid;
    var flexible = lib.flexible || (lib.flexible = {});
    
    if (metaEl) { // 如果名為viewport的mate標簽存在var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);  // 將name=viewport的mate標簽里的,content屬性里的initial-scale(初始縮放比)屬性處理成數組
        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); // IOS機
        var devicePixelRatio = win.devicePixelRatio; // 獲取window對象的 devicePixelRatio屬性值,這個屬性值就是我們所說的設備像素比,簡稱dpr
        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); // 給頁面根元素設置自定義屬性data-dpr,值為前面已經賦值好的dpr
    if (!metaEl) { // 當name=viewport的mate標簽不存在時,就給頁面添加一個,各元素值為前面計算好的scale,並不允許用戶拖動縮放
        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) { // 對於邏輯像素大於540的設備,其寬度就設置為設備像素比乘以540
            width = 540 * dpr;
        }
        var rem = width / 10; // 將屏幕寬度分成10份,每一份為1rem 所以整個屏幕的完整寬度為10rem
        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();
   // 后面這段代碼是將rem單位值轉換成px的和將px單位的值換算成rem單位的值
    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'] = {}));
復制代碼

 源碼的分析已經注釋到代碼后面的注釋中了。通過源碼的整體分析,我們會發現,lib-flexible的工作原理可以概括為:

  通過獲取設備像素比dpr進行運算,設置頁面里name=viewport的mate標簽(包括內部的縮放比例),再在頁面根元素--html上添加data-dpr屬性以及值,並且設置根元素字體大小,來進行頁面適配的。

  隨着技術的飛速發展,當前lib-flexible適配方案也在逐漸被更新的適配方案所替代,但是截止目前為止,還沒有發現哪種方案能完全滿足適配各種機型的需要,也會有一些小的問題。lib-flexible是目前用到的比較成熟的適配方案,所以,讓我們一起繼續探索吧~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM