使用REM实现移动端自适应缩放布局

猪小花1号2018-09-07 11:27

作者:刘新奇


1. 需求背景

在移动端功能、促销活动开发中,经常遇到设计师要求按设备屏幕宽度自动缩放界面元素的需求,比如:

  • 图片占满横向空间,高度按图片源大小等比自适应。
  • 背景图片预留位置,前端在该位置填写动态内容(比如文字),背景图会随设备宽度而缩放,因此内容也需要精准定位及缩放。
  • 整个界面所有元素都随viewport宽度自动缩放

这些需求,在移动设备屏幕宽度不定的情况下,还没有一个简单通用的方法,直接指定一个font-size或者高度,来让齐随设备宽度变化。

可能可行的方案1:

在我们直接使用固定viewport大小的情况下,这些需求处理起来比较简单, 比如设置viewport为:

<meta name="viewport" content="width=640,user-scalable=no">

但是这种方法的缺点也很明显,无法做到响应式了,部分情况还会有其他问题(比如边框丢失)。

另外我们很多项目的viewport设置如下:

<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">

如果改造成固定宽度方式,改造成本很大。

可能可行的方案2:

width=device-width的情况下, css有单位vw/vh可以使用,但是在Android平台的兼容性还不够好,iOS平台从iOS6就已经支持vw,兼容性参考。现在移动平台的兼容性一般要求iOS6及以上,Android 4.0及以上,所以vw还是不能直接用于项目中。

在研究了其它公司的缩放方案时,发现很多都是基于rem来做缩放的,接下来就开始rem方案的细节。

2. 基于REM的方案

rem(font size of the root element)是指相对于根元素的字体大小的单位。简单的说它就是一个相对单位。看到rem大家一定会想起em单位,em(font size of the element)是指相对于父元素的字体大小的单位。它们之间其实很相似,只不过一个计算的规则是依赖根元素一个是依赖父元素计算。

基本思路,使REM具有类似vw的功能,比如 1rem = 10vw(参考了淘宝的lib-flexible);

function refreshRem(){
    var width = docEl.getBoundingClientRect().width;
    if (width > 640) {
        width = 640;
    }
    var rem = width / 10;  //设定1rem = viewport width/10 = 10vw
    docEl.style.fontSize = rem + 'px';
    remCalc.rem = rem;
}

在页面head中执行这个函数,设定根节点的font-size 为 viewport width 的十分之一,这样页面中其它元素就可以使用rem来作为一个与页面宽度有关系的单位使用了。

那么问题来了,为什么不让1rem = 100vw 呢,这样的话在支持vw的设备上直接设置 html节点的font-size 为1vw 就可以搞定了,而且在后续vw的兼容性可以接受时直接去掉计算过程的就是。 之前碰到过问题,chrome在font-size 小于12px时还是会按12px计算,导致不准确(320/100 = 3.2了,就会碰到这种情况)。

现在rem与宽度有了关联,假设我们现在知道一张图片的宽高比为 2/1, 而且图片的宽度占据屏幕100%宽,那我们可以直接设定图片高度为5rem(因为宽度为10rem = 100% width),这样图片加载完成显示出来时,不会因为撑开垂直空间而导致页面出现跳动。

其次在需要页面元素与图片缩放配合的情况下,可以使用rem来进行定位及缩放; 比如如下需求: 视觉稿的为2倍大小, 640px宽;倒计时背景图覆盖整个宽度;

运营可配置的背景图为

倒计时需要开发动态添加,而且文案准确定位到图片的位置,文案字体大小要符合缩放要求, 这里我们可以用rem 或者百分比设置left,top来实现文字位置的定位, 文字的大小假设在2倍视觉稿上是32px,32px换算成rem,视觉稿宽度为640,1rem = 640/10 = 64px; 所以可以设置字体大小为 32/64 = 0.5rem; 这样font-size就会随设备宽度自动变化;

demo: http://codepen.io/satlxq/pen/vGxMmG 可能需要缩放预览窗口到小于等于640px

3. 缺点

  • 开发中增加了单位换算的难度。
  • rem被用来作为与宽度相关的单位,且根节点的font-size 也被重置,页面根据用户设置字体大小进行缩放的能力被抛弃。
  • 需要在页面头里边内嵌JS,对首屏加载性能有细微影响。

4. 结合考拉实际情况总结

调研了部分比较热门的网站,rem方案的采用率挺高,电商类的活动开发中运用尤多。

站点 使用情况
jumei.com 活动页面使用
mia.com 活动页使用
xiaohongshu.com 部分页面使用
3g.163.com 全站使用
taobao.com 全站使用
jd.com 未使用
tmall.com 部分地方用到
xiaomi.com 全站使用
weibo.com 未使用
qq.com 未使用

对于上面列出的缺点的分析

  • 单位换算可以在mcss中新增函数,用于将px值在编译时自动转换为rem值;如果布局中不需要rem缩放,还是可以直接使用原来的px以及百分比方式。
  • 根据设备设置进行字体放大的功能考拉暂无
  • 此段JS将直接内嵌到head中,无网络加载影响,其运行时间也少于5ms(基于iphone4 测试);

5. 引入rem缩放的方案

5.0 调整历史代码中使用rem为单位的代码。

5.1 JS引入

已经在公用common.ftl的宏中直接引入,每个页面会自动带上这段js,不需要手动引入; 初始化完成后会在window对象上新增属性, 具有三个方法及一个属性

window.remCalc = {
    refreshRem: Function,  //重新设置rem信息
    rem:  Number,  //当前1rem等于的px值
    rem2px: Function, //从rem换算为px
    px2rem: Function  //从px换算为rem
}

5.2 单位转换MCSS function

在公用文件_config.mcss 中引入加入

/*视觉稿px转rem计算函数*/
/*rem640对应宽度640px的2倍视觉稿,$px为视觉稿上的实际长度*/
$rem640 = ($px){
    @return $px / 64rem;
}
/*rem750对应宽度750px的2倍视觉稿,$px为视觉稿上的实际长度*/
$rem750 = ($px){
    @return $px / 75rem;
}

MCSS 文件需要引入:

@abstract '_config.mcss';

此文件中有两个方法,用于将相应视觉稿上的px值转换为rem值

$rem640 对应宽度640px的2倍屏视觉稿

$rem750 对应宽度750px的2倍屏视觉稿

以640px宽视觉稿为例,如果从视觉稿上量到width长度为400px, 可以如下设置css, 则会在编译的时候自动转换为rem值;

.testclass{
    width: $rem640(400);
    font-size: $rem640(32); //如果需要字体也按rem缩放,则可以直接按视觉稿上的字体大小传入参数,不需要除dpr值;
}

编译后的输出为:

.testclass{
    width: 6.25rem;
    font-size: 0.5rem; //如果需要字体也按rem缩放,则可以直接按视觉稿上的字体大小传入参数,不需要除dpr值;
}


网易云大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者刘新奇授权发布