HTML5移动端自适应解决方案

自接触移动端H5页面以来,从未停止对H5页面适配不同屏幕的解决方案的探索。从最初的bootstrap响应式框架来做手机适配; 后来尝试用百分比去做H5的适配;接着又去尝试媒体查询,但移动端的屏幕大小个各异,各种尺寸的机型都有,难以做到不同手机适配, 后来看到京东,网易,手淘等使用rem做手机适配,使用rem前端开发者可以很方便的在各种屏幕尺寸下,做出设计图要求的效果。本文重在说明 手机端不同的自适应解决方案的优缺点以及rem作为主流的移动端自适应布局方案的原理以及使用方案。

1. 使用rem做手机适配

什么是rem

rem是css3新增的相对长度单位,是指相对于根元素html的font-size值的大小

扩展: em也是css的相对长度单位,em作为font-size的单位时,其代表父元素的字体大小,em作为其他属性单位时,代表自身字体大小

rem适配原理

rem布局适配的本质是等比缩放,根据不同屏幕的宽度,以相同的比例动态修改html的font-size值,它跟设计师给出的设计图的大小没有关系,不管设计图给出的 是480,720,750,1080,都是将设计稿等比缩放在设备上;那么根html的font-size的值是怎么计算的呢?

  • 网易的方案是根据屏幕宽度与设计图宽度的比值作为font-size 大小,但此时计算出的font-size值小于12px会造成一些错误和奇怪的问题,为此我们把比例扩大100倍,即,根html的font-size值=屏幕宽度/设计图宽度*100 为了使比例不变,相应的设计图元素在变为rem的过程中要除以100;

  • 阿里的方案是根据设备像素比设置scale的值,保持视口device-width始终等于设备物理像素,接着根据屏幕宽度/10动态计算根html的font-size的大小, 设计稿的像素单位如何换成以rem为单位呢?可以用一个比例来计算,具体是将设计图分成100份,每一份的宽度用a来表示,同时认定1rem单位为10a,即1rem=10a; 如设计稿宽度为750px,此时:

750px=100a --- 1a=7.5px

1rem=10a --- 1rem=75px

同理,如果设计稿总宽度是640px,则1rem=64px。

rem适配使用方案

(function(doc, win) {
  var docEl = doc.documentElement,
    resizeEvt = ‘orientationchange‘ in window ? ‘orientationchange‘ : ‘resize‘,
    recalc = function() {
      var clientWidth = docEl.clientWidth;
      if(!clientWidth) return;
      docEl.style.fontSize = (clientWidth / 7.5) + ‘px‘;
    };
  if(!doc.addEventListener) return;
  win.addEventListener(resizeEvt, recalc, false);
  doc.addEventListener(‘DOMContentLoaded‘, recalc, false);
})(document, window);

小米移动端使用方案(基于720的设计图)

!function(e){
    var t = e.document,
        n = t.documentElement,
        i = e.devicePixelRatio || 1,
        a = "orientationchange" in e ? "orientationchange" : "resize",
        d = function(){
            var e = n.getBoundingClientRect().width || 360; //  Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置。
            (1 == i || e > 720) && (e = 720), n.style.fontSize = e/7.2 + "px" // 小米就是6啊 设计图都是安卓 720*1280界面
        };

    n.setAttribute("data-dpr", i),
    t.addEventListener && (e.addEventListener(a, d, !1), "complete" === t.readyState ||
    t.addEventListener("DOMContentLoaded", 
        function() { 
            setTimeout(d)
        }, !1)
    )

} (window)

flexible移动端使用方案

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

 

手机淘宝适配方案(基于750的设计图)

!function(e, t) { 
    var n = t.documentElement,
        d = e.devicePixelRatio || 1; // 设备DPR
    function i() { 
        var e = n.clientWidth / 3.75; // iPhone 6 布局视口375
        n.style.fontSize = e + "px" 
    } 
    if (function e() { t.body ? t.body.style.fontSize = "16px" : t.addEventListener("DOMContentLoaded", e)}(),
        i(),
        e.addEventListener("resize", i), 
        e.addEventListener("pageshow", function(e) { e.persisted && i() }), d >= 2){
        var o = t.createElement("body"), a = t.createElement("div");
        a.style.border = ".5px solid transparent", o.appendChild(a), n.appendChild(o), 
        1 === a.offsetHeight && n.classList.add("hairlines"), n.removeChild(o) 
    } 

}(window, document)

 

2. vw/vh

vw/vh是基于Viewport视窗的长度单位,这里的视窗(Viewport)指的就是浏览器可视化的区域,视口单位包括以下4个

  • vw : 1vw 等于视口宽度的1%
  • vh : 1vh 等于视口高度的1%
  • vmin : 选取 vw 和 vh 中最小的那个
  • vmax : 选取 vw 和 vh 中最大的那个

假如你的设计图是750px的宽度,从vw、vh的原理上看100vw=750px,即1vw=7.5px,我们可以根据设计图上的px值转换成对应的vw的值; 可以使用vw适配我们的页面的地方:

  • 容器适配,可以使用vw
  • 文本的适配,可以使用vw
  • 大于1px的边框、圆角、阴影都可以使用vw
  • 内距和外距,可以使用vw

兼容性问题(在移动端 iOS 8 以上以及 Android 4.4 以上获得支持,并且在微信 x5 内核中也得到完美的全面支持),所以仍有多种机型会存在 兼容性的问题,如果不考虑这些兼容性的问题,可以大胆使用vw。

3. 百分比

百分比做手机适配的方法是子元素相对于父元素的百分之多少,做手机端的适配,整体布局可以实现不同屏幕的缩放,但 元素内的字体以及位置难以做到不同屏幕上的放大和缩小,百分比布局只适合布局简单的页面,定制化要求比较高复杂的页面 实现很困难。

4 媒体查询

媒体查询是早期使用的手机适配方案,通过查询设备的宽度执行不同的css代码,展示设计图的UI; 针对移动和PC维护同一套代码时使用的较多,例如使用Bootstrap做响应式布局时,底层使用的就是媒体查询。 该方案缺点是代码量比较大,维护不方便,做定制化需求时不能很好的满足效果,以及为了兼顾移动端和PC端 各自特有的交互方式不能单独使用。

总结

随着移动端的发展会出现更多的适配方案,目前主流的仍是rem做手机端的适配,随着浏览器对vw的支持,使用vw做手机端的适配也是不错的选择, 如果你担心你的移动端项目有更好的兼容性以及更好的适配不同的设备,请选择rem作为你的适配方案,如果不用考虑兼容性的问题可以使用vw作为您的方案, 加入你的公司需要使用响应式布局移动和PC维护同一套代码,可以考虑使用基于媒体查询适配方案外加less和sass减少工作量。百分比做手机适配只能做简单 的页面。

参考资料

HTML5移动端自适应解决方案

上一篇:Css3如何实现旋转移动动画特效


下一篇:h5拉起手机摄像头拍照及选择图片并展示