移动端适配- dpr浅析


在上一片文章里,关于如何使用rem进行布局,我作了一个大概的描述。
今天这篇文字,主要说一个东西-dpr。

我们都知道,移动端开发,运行的平台主要是 ios 和 android ,做适配的时候,也会对不同的屏幕做一些特别的处理。

首先,先看一下这两家的屏幕吧。

移动端适配- dpr浅析_第1张图片
设备概况

眼花缭乱是不是,不要紧,我们只看我们需要的信息。
废话不多说,直接上结论:
IOS的移动设备:
iPhone4, 4s, 5, 5s, 6, 6+, 6s, 6s+ 都是Retina屏,
其中4, 4s, 5, 5s, 6, 6s的ppi都是326,dpr都是2,6+和6s+ppi是441,dpr是3。在ipad上同样只有dpr2和3两种的屏幕。

IOS设备 PPI DPR
iPhone4 326 2
iPhone4s 326 2
iPhone5 326 2
iPhone5s 326 2
iPhone6 326 2
iPhone6s 326 2
iPhone6 PLUS 441 3
iPhone6s PLUS 441 3
iPad - - 2
iPad - - 3

总的来说,ios设备的dpr总体来说比较一致。

再看看安卓:

android 尺寸 dpr
低清设备 1
- 1.5
- 1.75
- 2
mx2 800*1280 2.5
小米note 720*1280 2.75
- - 3
三星note4 - 4
... ... ...
移动端适配- dpr浅析_第2张图片

上一篇的末尾,提到了淘宝的移动端布局方案 Flexible.

源码如下:
//去除了头部css部分


! function(a, b) {
function c() {
var b = f.getBoundingClientRect().width;
b / i > 540 && (b = 540 * i);
var c = b / 10;
f.style.fontSize = c + "px", k.rem = a.rem = c
}
var d, e = a.document,
f = e.documentElement,
g = e.querySelector('meta[name="viewport"]'),
h = e.querySelector('meta[name="flexible"]'),
i = 0,
j = 0,
k = b.flexible || (b.flexible = {});
if (g) {
console.warn("将根据已有的meta标签来设置缩放比例");
var l = g.getAttribute("content").match(/initial-scale=([\d.]+)/);
l && (j = parseFloat(l[1]), i = parseInt(1 / j))
} else if (h) {
var m = h.getAttribute("content");
if (m) {
var n = m.match(/initial-dpr=([\d.]+)/),
o = m.match(/maximum-dpr=([\d.]+)/);
n && (i = parseFloat(n[1]), j = parseFloat((1 / i).toFixed(2))), o && (i = parseFloat(o[1]), j = parseFloat((1 / i).toFixed(2)))
}
}
if (!i && !j) {
var p = (a.navigator.appVersion.match(/android/gi), a.navigator.appVersion.match(/iphone/gi)),
q = a.devicePixelRatio;
i = p ? q >= 3 && (!i || i >= 3) ? 3 : q >= 2 && (!i || i >= 2) ? 2 : 1 : 1, j = 1 / i
}
if (f.setAttribute("data-dpr", i), !g)
if (g = e.createElement("meta"), g.setAttribute("name", "viewport"), g.setAttribute("content", "initial-scale=" + j + ", maximum-scale=" + j + ", minimum-scale=" + j + ", user-scalable=no"), f.firstElementChild) f.firstElementChild.appendChild(g);
else {
var r = e.createElement("div");
r.appendChild(g), e.write(r.innerHTML)
}
a.addEventListener("resize", function() {
clearTimeout(d), d = setTimeout(c, 300)
}, !1), a.addEventListener("pageshow", function(a) {
a.persisted && (clearTimeout(d), d = setTimeout(c, 300))
}, !1), "complete" === e.readyState ? e.body.style.fontSize = 12 * i + "px" : e.addEventListener("DOMContentLoaded", function() {
e.body.style.fontSize = 12 * i + "px"
}, !1), c(), k.dpr = a.dpr = i, k.refreshRem = c, k.rem2px = function(a) {
var b = parseFloat(a) * this.rem;
return "string" == typeof a && a.match(/rem$/) && (b += "px"), b
}, k.px2rem = function(a) {
var b = parseFloat(a) / this.rem;
return "string" == typeof a && a.match(/px$/) && (b += "rem"), b
}
}(window, window.lib || (window.lib = {}));

移动端适配- dpr浅析_第3张图片

很显然,这是经过混淆的代码。

经过一番挣扎,成功实现肉身还原。

核心方法如下:


if (metaEl) {
console.warn('将根据已有的meta标签来设置缩放比例');
var match = metaEl.getAttribute('content').match(/initial-scale=([\d.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale);
}
//如果在meta标签中,我们手动配置了flexible,则使用里面的内容
} 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 = window.navigator.appVersion.match(/android/gi);
var isIPhone = window.navigator.appVersion.match(/iphone/gi);
//devicePixelRatio这个属性是可以获取到设备的dpr的
var devicePixelRatio = window.devicePixelRatio;
if (isIPhone) {
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
dpr = 2;
} else {
dpr = 1;
}
} else {
dpr = 1;
// 其他设备仍旧使用1倍的方案
}
scale = 1 / dpr;
}

显而易见,这里只对ios作了处理,android 仍采用了1倍的布局方案。

个人觉得,android手机屏幕大小,宽高比是花开满地,要做的调整真的是太多了。如果根元素的font-size尺寸不对,页面效果不用多说。
就算把当前的设备信息都考虑进去了,那以后呢。
所以,考虑开发,维护,兼容性...淘宝这么做还是有道理的。

恩,也有看另一种方案,hotcss.js
http://imochen.github.io/hotcss/


(function(window, document) {

'use strict';

//给hotcss开辟个命名空间,别问我为什么,我要给你准备你会用到的方法,免得用到的时候还要自己写。
var hotcss = {};

(function() {
    //根据devicePixelRatio自定计算scale
    //可以有效解决移动端1px这个世纪难题。
    var viewportEl = document.querySelector('meta[name="viewport"]'),
        hotcssEl = document.querySelector('meta[name="hotcss"]'),
        dpr = window.devicePixelRatio || 1,
        maxWidth = 540,
        designWidth = 0;

    dpr = dpr >= 3 ? 3 : (dpr >= 2 ? 2 : 1);

    //允许通过自定义name为hotcss的meta头,通过initial-dpr来强制定义页面缩放
    if (hotcssEl) {
        var hotcssCon = hotcssEl.getAttribute('content');
        if (hotcssCon) {
            var initialDprMatch = hotcssCon.match(/initial\-dpr=([\d\.]+)/);
            if (initialDprMatch) {
                dpr = parseFloat(initialDprMatch[1]);
            }
            var maxWidthMatch = hotcssCon.match(/max\-width=([\d\.]+)/);
            if (maxWidthMatch) {
                maxWidth = parseFloat(maxWidthMatch[1]);
            }
            var designWidthMatch = hotcssCon.match(/design\-width=([\d\.]+)/);
            if (designWidthMatch) {
                designWidth = parseFloat(designWidthMatch[1]);
            }
        }
    }

    document.documentElement.setAttribute('data-dpr', dpr);
    hotcss.dpr = dpr;

    document.documentElement.setAttribute('max-width', maxWidth);
    hotcss.maxWidth = maxWidth;

    if (designWidth) {
        document.documentElement.setAttribute('design-width', designWidth);
        hotcss.designWidth = designWidth;
    }

    var scale = 1 / dpr,
        content = 'width=device-width, initial-scale=' + scale + ', minimum-scale=' + scale + ', maximum-scale=' + scale + ', user-scalable=no';

    if (viewportEl) {
        viewportEl.setAttribute('content', content);
    } else {
        viewportEl = document.createElement('meta');
        viewportEl.setAttribute('name', 'viewport');
        viewportEl.setAttribute('content', content);
        document.head.appendChild(viewportEl);
    }

})();

hotcss.px2rem = function(px, designWidth) {
    //预判你将会在JS中用到尺寸,特提供一个方法助你在JS中将px转为rem。就是这么贴心。
    if (!designWidth) {
        //如果你在JS中大量用到此方法,建议直接定义 hotcss.designWidth 来定义设计图尺寸;
        //否则可以在第二个参数告诉我你的设计图是多大。
        designWidth = parseInt(hotcss.designWidth, 10);
    }

    return parseInt(px, 10) * 320 / designWidth / 20;
}

hotcss.rem2px = function(rem, designWidth) {
    //新增一个rem2px的方法。用法和px2rem一致。
    if (!designWidth) {
        designWidth = parseInt(hotcss.designWidth, 10);
    }
    //rem可能为小数,这里不再做处理了
    return rem * 20 * designWidth / 320;
}

hotcss.mresize = function() {
    //对,这个就是核心方法了,给HTML设置font-size。
    var innerWidth = document.documentElement.getBoundingClientRect().width || window.innerWidth;

    if (hotcss.maxWidth && (innerWidth / hotcss.dpr > hotcss.maxWidth)) {
        innerWidth = hotcss.maxWidth * hotcss.dpr;
    }

    if (!innerWidth) {
        return false;
    }

    document.documentElement.style.fontSize = (innerWidth * 20 / 320) + 'px';

    hotcss.callback && hotcss.callback();

};

hotcss.mresize();
//直接调用一次

window.addEventListener('resize', function() {
    clearTimeout(hotcss.tid);
    hotcss.tid = setTimeout(hotcss.mresize, 33);
}, false);
//绑定resize的时候调用

window.addEventListener('load', hotcss.mresize, false);
//防止不明原因的bug。load之后再调用一次。


setTimeout(function() {
    hotcss.mresize();
    //防止某些机型怪异现象,异步再调用一次
}, 333)

window.hotcss = hotcss;
//命名空间暴露给你,控制权交给你,想怎么调怎么调。

})(window, document);

里面有这样的设置:

dpr = dpr >= 3 ? 3 : ( dpr >=2 ? 2 : 1 );

简单粗暴。

说到这里,说一下问题,也是我转去看flexible 代码的原因。
现在做的项目,使用的是hotcss , 然后出现了一个神奇的问题。

我的设备是iPhone5S ,系统版本8.3,微信版本6.3.15.
之前是6.3.13,更新到6.3.15之后,页面中的字体渲染完成之后,立刻会变大。刚开始怀疑是这些resize 的原因,断点调试,无果。
和腾讯的师兄说了一下,他并没有遇到这种情况。
然后,无意中发现,在微信字体设置中,改动一下字体,这种情况就没有了,恩,是在我的手机上没有了,在另一个同事的5s上依然有字体闪烁的情况。
我新建了一个分支,用上了flexible,表现良好,暂时没发现啥什么问题。


最后,附上我还原后的Flexible 源码, 有兴趣的小伙伴可以看看,希望能有所帮助。。


/**

  • Created by kk on 16/3/28.
    /
    /

    hotcss 可能有神奇的问题
  • */
    !function() {
    var innerStyle = "@charset "utf-8";html{color:#000;background:#fff;overflow-y:scroll;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}html *{outline:0;-webkit-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}html,body{font-family:sans-serif}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td,hr,button,article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{margin:0;padding:0}input,select,textarea{font-size:100%}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}abbr,acronym{border:0;font-variant:normal}del{text-decoration:line-through}address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:500}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:500}q:before,q:after{content:''}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}a:hover{text-decoration:underline}ins,a{text-decoration:none}",
    createStyle = document.createElement("style");
    if (document.getElementsByTagName("head")[0].appendChild(createStyle),
    createStyle.styleSheet)
    createStyle.styleSheet.disabled || (createStyle.styleSheet.cssText = innerStyle);
    else
    try {
    createStyle.innerHTML = innerStyle;
    } catch (ex) {
    createStyle.innerText = innerStyle;
    }
    }();

! function(window, nameSpace) {
var timer, doc = window.document,
docEl = doc.documentElement,
metaEl = doc.querySelector('meta[name="viewport"]'),
flexibleEl = doc.querySelector('meta[name="flexible"]'),
dpr = 0,
scale = 0,
Flexible = nameSpace.flexible || (nameSpace.flexible = {});
// 给Flexible 开创命名空间
//刷新rem
function refreshRem() {
var width = docEl.getBoundingClientRect().width;

    //width / dpr > 540 && (width = 540 * dpr);

    if(width / dpr > 540) {
        width = 540 * dpr ;
    }

    var rootSize = width / 10;
    docEl.style.fontSize = rootSize + "px",
     Flexible.rem = window.rem = rootSize;
}

if (metaEl) {
   console.warn('将根据已有的meta标签来设置缩放比例');
   var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
   if (match) {
       scale = parseFloat(match[1]);
       dpr = parseInt(1 / scale);
   }
   //如果在meta标签中,我们手动配置了flexible,则使用里面的内容
} 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 = window.navigator.appVersion.match(/android/gi);
    var isIPhone = window.navigator.appVersion.match(/iphone/gi);
    //devicePixelRatio这个属性是可以获取到设备的dpr的
    var devicePixelRatio = window.devicePixelRatio;
    if (isIPhone) {
        if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
            dpr = 3;
        } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
            dpr = 2;
        } else {
            dpr = 1;
        }
    } else {
        dpr = 1;
    }

    scale = 1 / dpr;
}

if (docEl.setAttribute("data-dpr", dpr), !metaEl)
    if (metaEl = doc.createElement("meta"),
            metaEl.setAttribute("name", "viewport"),            //j = scale            //j = scale              //j = scale
            metaEl.setAttribute("content", "initial-scale=" + scale + ", maximum-scale=" + scale + ", minimum-scale=" + scale + ", user-scalable=no"),
            docEl.firstElementChild)
        docEl.firstElementChild.appendChild(metaEl);
    else {

        var createDiv = doc.createElement("div");
        createDiv.appendChild(metaEl),
            doc.write(createDiv.innerHTML)
        }

window.addEventListener("resize", function() {
            clearTimeout(timer),
                timer = setTimeout(refreshRem, 300);
    }, !1),
        window.addEventListener("pageshow", function(a) {
        a.persisted && (clearTimeout(timer),
            timer = setTimeout(refreshRem, 300))
    }, !1),

    "complete" === doc.readyState ? doc.body.style.fontSize = 17 * dpr + "px" : doc.addEventListener("DOMContentLoaded", function() {
        doc.body.style.fontSize = 17 * dpr + "px"
    }, !1),
    refreshRem(),
    Flexible.dpr = window.dpr = dpr,
    Flexible.refreshRem = refreshRem,
    Flexible.rem2px = function(a) {
        var pxValue = parseFloat(a) * this.rem;
        return "string" == typeof a && a.match(/rem$/) && (pxValue += "px"), pxValue ;
    },
    Flexible.px2rem = function(a) {
        var remValue = parseFloat(a) / this.rem;
        return "string" == typeof a && a.match(/px$/) && (remValue += "rem"), remValue ;
    }

}(window, window.lib || (window.lib = {}));

运行截图:

移动端适配- dpr浅析_第4张图片
运行截图

亲测无误,希望能给大家带来一点帮助,谢谢。

你可能感兴趣的:(移动端适配- dpr浅析)