zoukankan      html  css  js  c++  java
  • rem布局原理深度理解(以及em/vw/vh)

    一、前言

      我们h5项目终端适配采用的是淘宝那套《Flexible实现手淘H5页面的终端适配》方案。主要原理是rem布局。最近和别人谈弹性布局原理,发现虽然已经使用了那套方案很久,但是自己对rem的理解很含糊, 包括vw、vh等。所以打算写博客总结一下,以加深理解。

    二、几个概念

      这里就不讲那些,物理像素、设备像素比了,可以自己查阅。要去理解rem/em/vm/vh等,首先要直观的去理解他们到底是什么?理解好了,后面就好办了。其实这几个都是css单位,就像我们常用的px一样,只不过他们都是相对单位。我平时使用的百分比单位如:100%;就是相对单位。

      2.1、em

      em作为font-size的单位时,其代表父元素的字体大小,em作为其他属性单位时,代表自身字体大小——MDN

      比如父元素font-size:12px;

      自身元素如果写成:font-sise:2em;则自身元素用px表示就是24px(相对父元素字体大小);

      但是自身元素设置:2rem,那么自身元素用px表示就是48px(相对自身字体大小);

           2.2、rem

      rem作用于非根元素时,相对于根元素字体大小;rem作用于根元素字体大小时,相对于其出初始字体大小——MDN

      比如根元素(html)设置font-size=12px; 非根元素设置2rem;则换成px表示就是24px;

      如果根元素设置成font-size=1rem;则根元素换成px就是相对于初始字体大小,一般是12px;

      2.3、vm/vh

      vw : 视口宽度的 1/100;vh :视口高度的 1/100 —— MDN

      在pc端,视口宽高就是浏览器得宽高;

      在移动端,这个还不太一样,不过一般设置: 

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

      代码以显示网页的屏幕宽度定义了视窗宽度。网页的比例和最大比例被设置为100%。

    三、剖析rem布局原理

      其实好好理解上面的概念,rem的原理也就很简单了。

      假设我们将屏幕平局分为10份,每一份宽度用一个a表示,即a=屏幕宽度/10;那么:

    div{ 5a} /* 屏幕宽度的50% */ 
    

    但是css中没有a这个单位啊?那怎么办呢?对,css不是有相对单位rem么?我们全可以实现借助rem代替上面的a。如:

    html {font-size: 12px}
    div { 2rem} /* 24px*/
    
    html {font-size: 16px}
    div { 2rem} /* 32px*/

    那么问题来了?怎么让html元素字体大小恒等于屏幕的1/10呢?如ipone6宽是375px,font-size:37.5px;

    html {fons-size: width / 10}
    div { 5rem} /* 5rem = 5a = 屏幕宽度的50% */ 

    我们用js很容易动态的设置html的font-size恒等屏幕的1/10;我们可以在页面dom ready、resize和屏幕旋转中设置:

    document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px'; 

    如何把设计稿的像素单位换成以rem为单位呢?可以用一个比例来计算:如设计稿宽度为750px,某个元素量得75px,那么:

    75px/750px = 计算所得rem/10rem,所以计算所得rem=75px;所以我们在样式中写1rem;实际宽度是75px;同理,如果设计稿总宽度是640px,则1rem=64px。

    预处理函数可以简化:

    $ue- 750; /* 设计稿图的宽度 */
    
    @function px2rem($px) {
      @return #{$px/$ue-width*10}rem;
    }
    
    div {
       px2rem(100);/*编译后:  p{1.5625rem}*/
    }

    四、rem万能吗?

      rem是一种弹性布局,它强调等比缩放,100%还原。它和响应式布局不一样,响应式布局强调不同屏幕要有不同的显示,比如媒体查询。

      字体并不合适使用rem, 字体的大小和字体宽度,并不成线性关系,所以字体大小不能使用rem;由于设置了根元素字体的大小,会影响所有没有设置字体大小的元素,因为字体大小是会继承的,难道要每个元素都显示设置字体大小?

      我们可以在body上做字体修正,比如把body字体大小设置为16px,但如果用户自己设置了更大的字体,此时用户的设置将失效,比如合理的方式是,将其设置为用户的默认字体大小:  

    html {fons-size: width / 10}
    body {font-size: 16px}  

      那字体咋整?我们可以用媒体查询和em来实现: 

    @media screen and (min- 320px) {
    	body {font-size: 16px}
    }
    @media screen and (min- 481px) and (max-640px) {
    	body {font-size: 18px}
    }
    @media screen and (min- 641px) {
    	body {font-size: 20px}
    }
    
    div {font-size: 1.2em}

      在制作H5的页面中,rem并不适合用到段落文本上。所以在Flexible整个适配方案中,考虑文本还是使用px作为单位。只不过使用[data-dpr]属性来区分不同dpr下的文本字号大小。

    .selector {
         2rem;
        border: 1px solid #ddd;
    }
    [data-dpr="1"] .selector {
        font-size: 14px;
    }
    [data-dpr="2"] .selector {
        font-size: 28px;
    }
    [data-dpr="3"] .selector {
        font-size: 42px;
    }
    

      当然你也可以给非根元素设置合适的字体。

    五、rem布局方案

      从上可以看出最好的弹性布局方案就是rem+js的方案,《Flexible实现手淘H5页面的终端适配》就是采用rem+js实现的。flexible主要做了几点。

    • 动态改写<meta>标签
    • <html>元素添加data-dpr属性,并且动态改写data-dpr的值。
    • <html>元素添加font-size属性,并且动态改写font-size的值

      https://github.com/amfe/lib-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'] = {}));
    

      

    六、em可以用来做弹性布局吗?

      上面知道,一旦某个节点的字体大小发生变化,其他节点也随之变化,所以不合适,但是用来处理字体还是绝妙的。

    七、vw/wh用来做弹性布局怎么样?

      根据上面说,vw —— 视口宽度的 1/100;vh —— 视口高度的 1/100;感觉已经不用多说了。

    /* rem方案 */
    html {font-size: width / 10}
    p { 1.5625rem}
    
    /* vw方案 */
    p { 15.625vw}

    如果rem方案中使用设置font-size=width/100就和vm意思一样, 但是比如font-size:640px/100;浏览器不会识别6.4px这么小的字体的。

    如果不考虑兼容性问题,可以大胆使用vw/vm做弹性的布局。

    八、网易考拉触屏版的js+rem的弹性布局方式

    其实它可以在body设置一个默认的字体大小的。

  • 相关阅读:
    SSL JudgeOnline 1194——最佳乘车
    SSL JudgeOnline 1457——翻币问题
    SSL JudgeOnlie 2324——细胞问题
    SSL JudgeOnline 1456——骑士旅行
    SSL JudgeOnline 1455——电子老鼠闯迷宫
    SSL JudgeOnline 2253——新型计算器
    SSL JudgeOnline 1198——求逆序对数
    SSL JudgeOnline 1099——USACO 1.4 母亲的牛奶
    SSL JudgeOnline 1668——小车载人问题
    SSL JudgeOnline 1089——USACO 1.2 方块转换
  • 原文地址:https://www.cnblogs.com/leaf930814/p/9059340.html
Copyright © 2011-2022 走看看