谈谈 rem 与 vw -- rem

谈谈rem与vw — rem

写这篇文章的原因,源于我在头条的面试。面试官问到了关于手机端适配rem的问题,这个问题非常有意思,想和大家分享一下:

Q:能谈谈rem的作用吗,与em有什么区别?
A: rem 是html 的font-size大小,一般为 16px, em 是父节点的 font-size 大小
Q: 我们为什么要用rem去做手机的适配?如果rem只是根节点字体大小的话,那么rem 其实和px 、em没有区别,rem解决了什么问题?

我之前确实做过几个简单的手机H5页面,只是在使用淘宝的lib-flexible方案,只觉得rem是解决方案的结果,但是从没想过为什么要使用rem来做手机端的适配。

当我再次去看淘宝的lib-flexible的时候,发现此方案已经不被推荐了,取而代之的是《如何在Vue项目中使用vw实现移动端适配》,于是想借此机会将 rem 与 vw 一起总结一下。

手机端适配模式

手机淘宝团队适配协作模式

一些基本概念

具体内容可以查看《使用Flexible实现手淘H5页面的最终适配》,在这里,我只对其中的一些概念谈一谈自己的理解。

viewport

简单来说,viewport严格等于浏览器视窗大小(不包含滚动条),但是在移动设备上有些复杂,分为 visualviewport 与 layoutviewport。这里不做详细解释,可以查看《viewports剖析》来了解详细内容,看完这篇文章,你会明白为什么在html头部 emmet 会自动帮你生成

<meta name="viewport" content="width=device-width, initial-scale=1.0">

设备独立像素(虚拟像素)

设备独立像素是软件概念上的一个像素,一个设备独立像素可以对应多个物理像素,对应关系由相关系统控制(我们不用管),CSS像素(即写在css中的1px)就是一种设备独立像素。

例如苹果是Retina屏,1px(虚拟像素) = 4px(物理像素,dpr=2)

盗一张手淘的图:

物理像素对虚拟像素

dpr

dpr实际上是一个比值,标识一个虚拟像素对应几个物理像素。正常屏幕1个 CSS 像素对应1个物理像素,dpr=1,Retina 屏幕1个 CSS 像素对应 n^2 个物理像素,dpr 为 n。例如上面那张图 dpr 就是2,因为一个 CSS 像素对应 4 个物理像素, 长对应2个,宽对应2个。

rem

rem 简单来说是 html 的 font-size 大小,一般默认为 16px,与 em 的父节点 font-size 大小不同。一般 rem 方案解决小屏幕适配问题就是通过 JavaScript 动态改变 html 元素的 font-size 大小进行适配(组件使用 rem 作为度量)

lib-flexible

lib-flexible是淘宝团队推出的一种 rem 适配方案,它帮我们解决了不同屏幕如何设置 html 的 font-size 大小与 dpr 的问题。

可以看到 lib-flexible 里面有两个版本, master 里面用正则匹配 IOS 端和 Android 端,而Branch 2.0 版本则取消了对于 dpr 的适配,具体我会在下面进行介绍。

rem 解决了什么问题

首先要明确的一点是 data-dpr 和 rem 解决的是两个维度的问题。 Rem 解决的是移动端适配的问题, data-dpr 是在解决了移动端适配问题的基础上用于提升视觉效果。

在 Web App 上面,我们需要禁止页面缩放。


手淘webapp.jpeg

可以试着打开一下手机淘宝页面,你会发现你无法将这个页面放大。

考虑一个问题,你需要为一个 375px 大小的屏幕做页面,页面充满整个屏幕, 里面有一个 200px 大小的弹窗,你需要如何去实现它?

.wrapper {
  width: 375px;
  
  .dialog {
    width: 200px;
  }
}

如果这么做就 GG 了,当这个页面放到大一些的屏幕上(例如 414px的屏幕),那么显示出来的效果会出现留白。 414 是 iPhone * Plus 的尺寸,如果放到 ipad 上面那么留白会更大。

我们如何解决这个问题呢?

如果能够有一种动态的修改元素的长度就好了。

rem 就是这样一种原理,我们的组件使用 rem 进行编写,然后使用 JavaScript 动态设置 rem 到 px 的映射关系,这样就可以实现对于不同手机页面的适配了。

上 lib-flexible 源码

// 经过精简
var docEl = document.documentElement;
function setRemUnit () {
    var rem = docEl.clientWidth / 10
    docEl.style.fontSize = rem + 'px'
 }

具体 rem 的计算我会在下面介绍,核心代码实际上就是为 html 元素设置一个 font-size 而已。

lib-flexible 设置 dpr 又是为了什么?

为了解决“极致的1px”问题。

再次把这张图放一下:

物理像素对虚拟像素

在 Retina 屏幕上面,1个 CSS 像素实际上对应着4个物理像素(dpr=2的情况)。当我们在屏幕上画1px 的线时,Retina 屏幕实际上是2个物理像素宽。我们要实现“极致的1px”就是希望在Retina屏幕上画出一条1个物理像素宽的细线。

原理其实也很简单,对于 dpr=2的设备,设置 rem 的值为正常值的 2 倍,将需要画出“极致的线”的地方使用1px表示,再将页面缩小2倍,这样1px的线就变成了“0.5px”,实际上为1个物理像素来表示。

同理,对于 dpr=n 的设备,将 rem 值设为正常的 n 倍,在对页面缩放 n 倍。

现在的 lib-flexible 有两个版本,master 版本和 2.0 版本。在 master 版本中使用 正则来判断系统种类(IOS/Andorid),但是只对 IOS 系统做了 dpr 的适配,对于 Android 手机,统一设置 dpr =1;

实际上使用 rem 配合 dpr 缩放的方式有非常多的问题。最明显的例子就是安卓机 dpr 的混乱,例如 VIVO 的某款手机甚至出现了 dpr 为小数的情况(上文我们介绍到,1个 CSS 像素对应多少个物理像素 dpr 就是几,显然不可能出现小数位的情况),所以使用缩放来实现“极致的1px”兼容性并不是很好。

有团队的做法是将 dpr 设置有问题的机型进行上报,然后收集成一个白名单,再根据白名单在 lib-flexible 对于有问题的机型进行单独适配,然而这个工作量一般的小团队没有精力去做……

所以我们可以看到 2.0 版本已经舍弃了这种做法。

// 进行了精简
// detect 0.5px supports
var docEl = document.documentElement;
if (dpr >= 2) {
  var fakeBody = document.createElement('body')
  var testElement = document.createElement('div')
  testElement.style.border = '.5px solid transparent'
  fakeBody.appendChild(testElement)
  docEl.appendChild(fakeBody)
  if (testElement.offsetHeight === 1) {
    docEl.classList.add('hairlines')
  }
  docEl.removeChild(fakeBody)
}

可以看到这个版本对于设备进行了一个测试,如果该设备支持 0.5px 的书写方式,那么就在 html 元素上面添加一个 hairlines 的类,我们在使用的时候只需要在header中添加.hirlines div {border-width: 0.5px}就可以了。

例如:

// some device support 0.5 px
<!DOCTYPE html>
<html lang="en" class="hairlines">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
      div {
        boder: 1px solid #000;
      }
      .hairlines div {
        border: 0.5px solid #000;
      }
    </style>
</head>
<body>
    <div>我是极致的1px</div>
</body>
</html>

这样,如果支持 0.5 px,就会出现一条非常细的线,否则就按照正常的显示方式。

Rem的映射方式

这个问题其实很简单,首先我们设定一个基于设计稿的 rem 基准值,然后在真实设备上乘一个设计稿对真实设备的比值:

rem转化公式.png

而 lib-flexible 则默认规定了 rem 的取值为屏幕宽度的十分之一

Rem的一些问题

使用 rem 方式布局其实有很多问题。我们使用 rem 转换出来的 px 往往有很多小数点,所以最终的展示效果的 CSS 长宽实际上是经过四舍五入的。因此往往我们需要手动去调整“最后的1px”,这给我们带来了很多的麻烦。

因此 rem 确实是对移动端适配的一种解决方案,但并非无懈可击。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容