rem和px之间的转换

11,661 阅读5分钟

移动端rem的适配

px: 像素,它是一个固定大小的单元,像素的计算是针对屏幕的,一个像素(1px)就是屏幕的一个点,但是因为它是固定大小的,所以不能够自适应;

em: 相对长度单位,它是用来设置文本的字体尺寸,相对于当前对象内的文本的字体尺寸;一般浏览器默认(1em=16px);

em是相对于父级元素的单位,会随父级元素的属性(font-size或其他属性)变化而变化;

rem: (css3新增的相对长度单位),相对于根目录,即HTML元素,所以只要在HTML标签上设置字体大小,文档中的字体大小都会以此为参照标准,一般用于自适应布局;

rem是相对于根目录(HTML)的,所以它会随HTML的元素的属性(font-size)变化而变化

了解了这三个单位的基本概念;

在移动端开发的时候一般为了适应不同屏幕的大小,我们需要使用自适应布局;

1.首先第一步便是文档根元素HTMLfont-size的设置

浏览器一般默认字号为16px,我们可以通过设置<html>的font-size来设置我们的参考值; 看一下他们的转换关系:

pxrem
1212/16 = 0.75
1414/16 = 0.875
1616/16 = 1
...

了解了根元素font-size的换算关系,我们就可以为<html>设置font-size,从而自适应布局了

html {
  font-size: 100% // 相当于16px
}
div {
  width: 3rem  // 相当于3*16px = 48px
}

2.在编辑器vscode下载插件px2rem

了解了rem的换算关系后,如果我们每次都要根据换算关系来计算rem的值,也是很烦躁的,所以可以借助插件,类似的插件有很多,我这里用的是px2rem的这个插件,直接在vscode的插件库下载安装就好;

然后配置一下:

image.png

图上的root font size即你要配的html的font-size的大小,默认是16px,可根据设计图和项目自行配置,我这里配置的是37.5;

Fixed Digits是对于rem如果除不尽的话,取得小数,默认是六位,我这里取得是小数点后四位; 其它的配置也可自行搜索配置;

这样我们在写css样式的时候就会自动提示rem的对应值了;

这里推荐一篇文章:关于移动端适配:

3.全局js文件,可参照插件lib-flexible

通过这种方式,我们写css的时候局可以直接写px,该文件会在渲染的时候自动给我们转换成对应的rem,我们直接写px就可以了;

文件自行在网上查找;

4.安装插件 lib-flexible

lib-flexuble插件是移动端适配的一种解决方案,是淘宝项目组开发出来的,属于开源项目,可以在各类项目中引用;

lib-flexuble它会自动将html中的font-size设置为屏幕大小的十分之一,即假设说屏幕大小为750px,那么html的font-size为75px,即1rem=75px,那么我们要设置的元素的宽度如果为150px,就可以设置为2rem;

以下是在react项目中的使用:

一般来讲,lib-flexible会和px2rem-loader插件一起使用,目的是将css中的px转换为rem;

4.1 安装lib-flexible

npm install lib-flexible --save-dev

4.2 在项目src目录下的index.js中引入

import 'lib-flexible';

4.3 安装px2rem-loader

关于该插件的配置可查看这篇文章:# lib-flexible适配大屏方案(附移动端适配)

npm install px2rem-loader --save-dev

4.4 分析一下lib-flexible

lib-flexible插件的源码一共100来行,我们一起分析分析吧!


;(function(win, lib) {
    var doc = win.document; // 返回文档对象
    var docEl = doc.documentElement; // 文档对象的根元素html
    var metaEl = doc.querySelector('meta[name="viewport"]'); // 寻找第一个name为viewport的meta标签
    var flexibleEl = doc.querySelector('meta[name="flexible"]');// 寻找第一个name为flexible的meta标签
    var dpr = 0; // 客户端设备的像素比(与每个CSS像素相对应的物理设备像素的数量)
    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; // 返回当前显示设备的物理分辨率与css像素分辨率之比。
        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);
    // 如果没有设置视口的meta标签,就根据我们计算的值设置
    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);
        }
    }
    
    // 根据屏幕的宽度,设置rem
    function refreshRem(){
        var width = docEl.getBoundingClientRect().width;
        // 这一块会有个小坑,大多数状况是都可以适配的,但是dpr的值一般都是1,当大屏的时候,布局就会全部乱掉,所以如果出现这种状况,可以根据需求修改这块的代码;
        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);
    // 判断文档对象的加载状态,当文档和所有子资源已完成加载时
    // 重置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);
    }


    refreshRem();

    flexible.dpr = win.dpr = dpr;
    flexible.refreshRem = refreshRem;
    // 将rem转换为px
    flexible.rem2px = function(d) {
        var val = parseFloat(d) * this.rem;
        if (typeof d === 'string' && d.match(/rem$/)) {
            val += 'px';
        }
        return val;
    }
    // 将px转换为rem
    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'] = {}));