换一种rem方式进行移动端布局

公司的移动端项目,包括app内的webview页面和Wap端网站,均采用rem进行布局,不过并不是基于flexible.js通过js去设置根字体大小,而是采用纯CSS实现的rem布局,在项目中的效果还不错,下面简单整理一下。

<!--more-->

其实早前已经在github开了一个项目,这里只是把相关内容补充一下。

参考:

1. 实现原理

rem布局的本质是等比缩放,其基本原理就是通过html根字体大小去设置布局尺寸的大小,不同的分辨率对应不同的根字体,这样就可以按比例还原设计图了。

当然这里我们讨论的不是rem布局的原理,而是如何基于CSS去实现rem布局,其实现十分简单。

$design-width: 750;
html {
    font-size: calc((100vw / $design-width) * 100);
}

分别解释一下:

  • 100vw表示屏幕的宽度
  • $design-width表示设计图尺寸,项目中一般为750px
  • calc,CSS3新增的属性,通过计算来决定一个CSS属性值,参考MDN文档

上面的样式声明了html的字体大小(即rem值),其值为窗口大小 / 设计图尺寸 的100倍,之所以设置为100倍,是为了防止浏览器的最小字体限制导致的渲染错误。 仅仅通过上面这一段代码,就可以根据屏幕分辨率,自动计算rem大小,然后只需要在布局中采用rem作为尺寸单位,即可实现rem布局,是不是很神奇!

2. 设计图尺寸

为什么要将rem大小与设计图尺寸相关联呢?这样处理的话,我们可以直接将设计图标注的尺寸编写到样式表中,而无需再次计算对应的换算关系。

可以通过下面这个scss函数将设计图尺寸转换为rem

@function rem($px) {
    @if (unitless($px)) {
        @return 1rem * ($px/100);
    } @else {
        @return 1rem * ($px/100px);
    }
}

其中$px参数即为设计图标注的尺寸,样式表中只需要编写如下代码即可

.box {
    width:rem(200)
}

其中,200为750标准设计图的标注尺寸。这样基本可以完美还原设计图。甚至在微信小程序中的开发中,可以通过将rem单位修改为rpx(这个是小程序本身的rem实现单位),实现样式表的无缝迁移~

其实现在有相关的postCSS插件可以实现自动将px转换为rem单位,比如pxtorem

但是在由于公司项目的特殊性(文字阅读),某些地方还是依赖于px进行单位设置,因此我更倾向于通过这个rem函数来进行手动控制样式表。根据过去的项目经验,敲一个rem(200)200px然后进行pxtorem的转换,工作成本其实是差不多的。

3. 最大尺寸

使用rem布局的一个问题是在PC端打开的移动端页面有些奇怪,。因为PC的宽度比较大,因此计算得到的rem也会比较大,导致设计图放大到了一个不太合理的比例。

我的解决办法是给定一个页面宽度的最大值

@media screen and (min-width:800px) {
    html {
        font-size: 100px;
    }
    body {
        width: 800px;
        margin: 0 auto;
    }
}

这样的话即使在很大的屏幕上,整个页面也能比较合理的展示。

但是会引入另外一个问题,即fixed的元素可能会溢出容器,在某些fixed定位、宽度为100%的DOM上,表现的就比较奇怪了,因为他们的包含块是窗口本身,处理方式是定义一个rem-fixed的颗粒类

@media screen and (min-width:800px) {
   .rem-fixed {
       width: 800px;
       left: 0;
       right: 0;
       margin-left: auto;
       margin-rigth: auto;
   }
}

在某些特定的布局上可能会有些问题,按需处理即可。之前张鑫旭大神在博客中提出了一个更合理的实践方案

html {
    font-size: 16px;
}

@media screen and (min-width: 375px) {
    html {
        /* iPhone6的375px尺寸作为16px基准,414px正好18px大小, 600 20px */
        font-size: calc(100% + 2 * (100vw - 375px) / 39);
        font-size: calc(16px + 2 * (100vw - 375px) / 39);
    }
}
@media screen and (min-width: 414px) {
    html {
        /* 414px-1000px每100像素宽字体增加1px(18px-22px) */
        font-size: calc(112.5% + 4 * (100vw - 414px) / 586);
        font-size: calc(18px + 4 * (100vw - 414px) / 586);
    }
}
@media screen and (min-width: 600px) {
    html {
        /* 600px-1000px每100像素宽字体增加1px(20px-24px) */
        font-size: calc(125% + 4 * (100vw - 600px) / 400);
        font-size: calc(20px + 4 * (100vw - 600px) / 400);
    }
}
@media screen and (min-width: 1000px) {
    html {
        /* 1000px往后是每100像素0.5px增加 */
        font-size: calc(137.5% + 6 * (100vw - 1000px) / 1000);
        font-size: calc(22px + 6 * (100vw - 1000px) / 1000);
    }
}

这里的缺点在于:尺寸并不是完全按照设计图的比例还原,如果设计师那边没问题,这种处理方式更优,因为页面能在PC上也能铺满整个屏幕。

4. 向后兼容

上面的实现完全由CSS控制,不需要根据JavaScript来动态计算根字体的大小。但是仍旧需要考虑calc兼容性的问题。这里是关于calc的兼容列表,可以看见基本满足生产条件了。

在需要适配某些不支持calc特性的版本下(比如在Android V4.4.4以下版本不支持乘法和除法计算),可以使用JavaScript向下兼容(虽然很不情愿这么干~)

通过js计算rem大小比较容易

var newRem = function() {
    var html = document.documentElement
    // 设计图标准750px
    var baseWidth = 750
    // 屏幕最大宽度800px
    var screenWidth = Math.min(800, html.getBoundingClientRect().width)
    html.style.fontSize = screenWidth / 750 * 100 + 'px';
};

我们只需要在不支持calc特性的浏览器上调用newRem函数即可,下面是自己写的一个hack方法,暂时没找到更好的做法~

// 获取元素的计算样式
function getStyle(dom, key){
    var style
    if (window.getComputedStyle) {
        style = window.getComputedStyle(dom, null)
    } else { 
        style = dom.currentStyle  // fuck IE
    }

    return style[key]
}

// 判断浏览器是否支持calc
function isSupportCalc(){
    var oDiv = document.createElement("div");
    oDiv.style.width = 'calc((100vw / 750) * 100)'

    document.body.appendChild(oDiv)
    var width = getStyle(oDiv, 'width')
    oDiv.remove()

    var diff = 0.5

     width = parseFloat(width)
     var calcWidth = window.screen.availWidth / 750 * 100

     return width && Math.abs(calcWidth - width) < diff
}

// 判断浏览器calc特性
var isSupport = isSupportCalc()
if (!isSupport){
    window.addEventListener('resize', newRem, false);
    newRem()
}

OK,这样就解决了calc不兼容或vw不兼容导致的页面排版异常的情形。

5. 小结

之所以采用这种方式实现rem,最主要的原因是:非常酷。我一直坚信着能不用JS实现的CSS效果,就不用CSS实现。

毫无疑问,基于calc和vw实现的rem布局,刚好满足这个条件。因此我义无反顾地在项目中使用了这种形式,当然,为了向后兼容,还是需要引入相关的polyfill的,毕竟技术是为了业务服务的,我们还是需要对项目和用户负责的哈哈。