- Published on
使用REM实现移动端自适应缩放布局
- Authors
- Name
- Will Liu
- @satlxq111
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 就会随设备宽度自动变化;
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 测试);
为提高活动开发效率,个人认为可以全站引入 rem 缩放方案
5. 引入 rem 缩放的方案
5.1 JS 引入
已经在 common.ftl 的 meta 宏中直接引入,每个页面会自动带上这段 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值;}