rem 组件

移动端开发中,有时需要使用rem作为尺寸单位来做各个机型的适配。

如下介绍的是如何设置rem和通过less函数来做浏览器适配。

使用方式

// 引入 rem/index.js 去动态修改 rootElement 的 font-size 值为屏幕款的 1/10
import rem from './rem';
1
2
/* 引入 rem/index.less 以便使用 .rem 函数 */
@import '.rem/index.less';

/* 其中 640 为该元素的设计稿宽度,单位是px(设计稿宽度为 640px,如需修改,可更改 rem/index.less 文件) */
.class-name {
    .rem(width, 640);
}
1
2
3
4
5
6
7
/**
 * @file 初始化页面时设置 html 元素的 font-size 为屏幕宽度的 1/10
 *       即屏幕宽度 = 10rem
 * @author: wind-stone<wind-stone@qq.com>
 */
(function(window) {
    var document = window.document;
    var rootElement = document.documentElement;

    if (document.readyState === 'complete') {
        setBodyFontSize();
    } else {
        document.addEventListener('DOMContentLoaded', completed, false);
        document.addEventListener('load', completed, false);
    }

    setRootElementFontSize();

    /**
     * 设置 html 根元素的 font-size
     */
    function setRootElementFontSize() {
    // 不能这样获取屏幕宽度,因为在低端 OPPO & VIVO 手机上会返回 980
    // var rootElementWidth = window.innerWidth

        // 也可以这样获取屏幕宽度
        // var rootElementWidth = document.documentElement.clientWidth
        var rootElementWidth = rootElement.getBoundingClientRect().width;
        var pxPerRem = rootElementWidth / 10;
        rootElement.style.fontSize = pxPerRem + 'px';
    }

    /**
     * 设置 body 元素的 font-size(以便继承 font-size)
     */
    function setBodyFontSize() {
        document.body.style.fontSize = '14px';
    }

    /**
     * DOMContentLoaded/load 完成之后执行
     */
    function completed() {
        document.removeEventListener('DOMContentLoaded', completed);
        document.removeEventListener('load', completed);
        setBodyFontSize();
    }
})(window);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
 * @file rem 函数
 * @author: wind-stone<wind-stone@qq.com>
 * 使用示例:.rem(width, 640); 640为设计稿宽度,可以修改为需要的值
 */

.rem(@name, @px){
    @{name}: @px / 640 * 10rem;
}
1
2
3
4
5
6
7
8
9

Android 设置系统字体大小影响 rem 的值

在开发 APP 内的 h5 页面且使用上述的rem布局时,若改变 Android 机型的系统字体大小后,会导致页面布局错乱,其原因是,系统字体大小的改变,会覆盖上述用户自己修改的htmlfont-size(但覆盖的时机不确定),网上有如下几种解决方案。

参考:rem布局在webview中页面错乱open in new window

方案一: 客户端固定 webview 的默认字体大小(推荐)

安卓客户端通过webview配置webview.getSettings().setTextZoom(100)就可以禁止缩放,按照百分百显示。

方案二: 获取系统字体大小后改写 html 的大小为百分比

此方案会先获取到系统字体大小,再将html元素的font-size设置为基于系统字体大小的百分比数值,以最终达到想要的px值。

/**
 * @file 初始化页面时设置 html 元素的 font-size 为屏幕宽度的 1/10
 *       即屏幕宽度 = 10rem
 * @author: wind-stone<wind-stone@qq.com>
 */
(function(window) {
  var document = window.document;
  var rootElement = document.documentElement;

  if (document.readyState === 'complete') {
    setBodyFontSize();
  } else {
    document.addEventListener('DOMContentLoaded', completed, false);
    document.addEventListener('load', completed, false);
  }

  setRootElementFontSize();

  /**
     * 获取系统默认字体大小
     */
  function getSystemDefaultFontSize() {
    var d = window.document.createElement('p');
    d.style.width = '1rem';
    d.style.display = 'none';
    var head = window.document.getElementsByTagName('head')[0];
    head.appendChild(d);
    var defaultFontSize = parseFloat(
      window.getComputedStyle(d, null).getPropertyValue('width')
    );
    return defaultFontSize;
  }

  /**
     * 设置 html 根元素的 font-size
     */
  function setRootElementFontSize() {
    // 不能这样获取屏幕宽度,因为在低端 OPPO & VIVO 手机上会返回 980
    // var rootElementWidth = window.innerWidth

    // 也可以这样获取屏幕宽度
    // var rootElementWidth = document.documentElement.clientWidth
    var rootElementWidth = rootElement.getBoundingClientRect().width;
    var pxPerRem = rootElementWidth / 10;

    // rootElement.style.fontSize = pxPerRem + 'px';
    rootElement.style.fontSize =
            (rootElementWidth / getSystemDefaultFontSize()) * 100 + '%';
  }

  /**
     * 设置 body 元素的 font-size(以便继承 font-size)
     */
  function setBodyFontSize() {
    document.body.style.fontSize = '14px';
  }

  /**
     * DOMContentLoaded/load 完成之后执行
     */
  function completed() {
    document.removeEventListener('DOMContentLoaded', completed);
    document.removeEventListener('load', completed);
    setBodyFontSize();
  }
})(window);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

此方案尽管经过测试是有效的,但是因为没有任何资料显示设置系统字体大小后系统覆盖htmlfont-size的时机,会存在通过setRootElementFontSize设置htmlfont-size之后系统再覆盖字体大小的可能。

PS: 关于这种方法,我猜测在getSystemDefaultFontSize方法里获取div元素的宽度时,就已经确定了系统默认字体大小了,因此之后改写htmlfont-size,就不会再被系统覆盖。

方案三: 先设置再读取,不一致的话,重新设置

此方案是先按要求设置htmlfont-size,再通过 DOM API 读取出来,对比设置值和读取值,若不一致,再按照比例再设置一次。

function htmlFontSize(){
    var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
    var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    var width = w > h ? h : w;
    width = width > 720 ? 720 : width
    var fz = ~~(width*100000/36)/10000
    document.getElementsByTagName("html")[0].style.cssText = 'font-size: ' + fz +"px";
    var realfz = ~~(+window.getComputedStyle(document.getElementsByTagName("html")[0]).fontSize.replace('px','')*10000)/10000
    if (fz !== realfz) {
        document.getElementsByTagName("html")[0].style.cssText = 'font-size: ' + fz * (fz / realfz) +"px";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

~是 NOT 操作符。对于浮点数,~~value可以代替parseInt(value),且效率更高。

假设fz100,得到的realfz200,则将htmlfont-size设置为50时,最终得到的realfz就为一开始想要的fz100了。