前端 rem适配方案

rem方案

原理

rem是相对长度单位,rem方案中的样式设计为相对于根元素font-size计算值的倍数。根据 屏幕宽度 设置html标签的font-size,在布局时使用 rem 单位布局,达到自适应的目的,是 弹性布局 的一种实现方式。

实现过程: 首先获取文档根元素和设备dpr,设置 rem,在html文档加载和解析完成后调整body字体大小; 在页面缩放 / 回退 / 前进的时候, 获取元素的内部宽度 (不包括垂直滚动条,边框和外边距),重新调整 rem 大小。

实现方法:css 处理器或 npm 包将页面 css 样式中的px自动转换成 rem。在整个 flexible 适配方案中,文本使用px作为单位,使用[data-dpr]属性来区分不同dpr下的文本字号。由于手机浏览器对字体显示最小是8px,因此对于小尺寸文字需要采用px为单位,防止通过 rem 转化后出现显示问题。手机淘宝 中的字体使用px为单位,腾讯新闻中的字体使用rem为单位。

贴上 源码 分析

(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) { 
    //当meta中viewport的标签设置了scale时,将根据scale手动设置dpr
        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) {   
    //当meta中flexible的标签存在时,据此设置dpr
        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) { 
    //根据js获取到的devicePixelRatio设置dpr及scale,scale是dpr的倒数
        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和3倍方案
            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);
    //文本字号不建议使用rem,flexible适配方案中,文本使用px作为单位,使用[data-dpr]属性来区分不同dpr下的文本字号
    
    if (!metaEl) {
    //添加meta标签,设置name为viewport,content根据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(){
        //更新rem值
        var width = docEl.getBoundingClientRect().width;
        if (width / dpr > 540) {
            width = 540 * dpr;
        }
        var rem = width / 10; //1rem = viewWidth / 10
        docEl.style.fontSize = rem + 'px';
        flexible.rem = win.rem = rem;
    }
    
    //resize与pageshow延时300ms触发refreshRem(),使用防抖函数,防止事件被高频触发可能引起性能问题
    win.addEventListener('resize', function() {
        clearTimeout(tid);
        tid = setTimeout(refreshRem, 300);
    }, false);
    win.addEventListener('pageshow', function(e) {
        //当一条会话历史纪录被执行的时候触发事件,包括后退/前进按钮,同时会在onload页面触发后初始化页面时触发
        if (e.persisted) {//表示网页是否来自缓存
            clearTimeout(tid);
            tid = setTimeout(refreshRem, 300);
        }
    }, false);

    //在html文档加载和解析完成后设置body元素字体大小
    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);
    } 
    //浏览器有最小字体限制,css在pc上font-size是12px(移动端最小是8px), 也就是css像素是12,其DPR为1,在移动端dpr有可能为2和3,为了保证字体不变小,需要用12*dpr进行换算。
   
    refreshRem();

   //实现rem与px相互转换
    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'] = {}));

优势

兼容性好

  • ios: 6.1系统以上都支持

  • android: 2.1系统以上都支持

  • 大部分主流浏览器都支持

    1.png

  • 相较于之前的静态布局和百分比方案,页面不会因为伸缩发生变形,自适应效果更佳。

不足

  • 不是纯css移动适配方案,需要引入js脚本 在头部内嵌一段 js脚本 监听分辨率的变化来动态改变根元素的字体大小css样式和 js 代码有一定 耦合性,并且必须将改变font-size的代码放在 css 样式之前。
    ...
  • 小数像素问题,浏览器渲染最小的单位是像素,元素根据屏幕宽度自适应,通过 rem 计算后可能会出现小数像素,浏览器会对这部分小数四舍五入,按照整数渲染。浏览器在渲染时所做的摄入处理只是应用在元素的尺寸渲染上,其真实占据的空间依旧是原始大小。也就是说如果一个元素尺寸是 0.625px,那么其渲染尺寸应该是 1px,空出的 0.375px 空间由其临近的元素填充;同样道理,如果一个元素尺寸是 0.375px,其渲染尺寸就应该是0,但是其会占据临近元素 0.375px 的空间。会导致:缩放到低于1px的元素时隐时现(解决办法:指定最小转换像素,对于比较小的像素,不转换为 remvw);两个同样宽度的元素因为各自周围的元素宽度不同,导致两元素相差1px;宽高相同的正方形,长宽不等了;border-radius: 50% 画的圆不圆。
    ...
  • Android 浏览器下 line-height 垂直居中偏离的问题。常用的垂直居中方式就是使用line-height,这种方法在Android设备下并不能完全居中。
    ...
  • cursor: pointer 元素点击背景变色的问题,对添加了cursor:pointer属性的元素,在移动端点击时,背景会高亮。为元素添加tag-highlight-color:transparent 属性可以隐藏背景高亮。

作者:jluemmmm
链接:https://www.jianshu.com/p/2c33921d5a68
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted @ 2022-02-23 22:45  波吉国王  阅读(673)  评论(0编辑  收藏  举报