谈谈 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 一起总结一下。

手机端适配模式

谈谈 rem 与 vw -- rem_第1张图片
手机淘宝团队适配协作模式

一些基本概念

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

viewport

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


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

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

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

盗一张手淘的图:

谈谈 rem 与 vw -- rem_第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 上面,我们需要禁止页面缩放。


谈谈 rem 与 vw -- rem_第3张图片
手淘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”问题。

再次把这张图放一下:

谈谈 rem 与 vw -- rem_第4张图片
物理像素对虚拟像素

在 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



    
    
    
    Document
    


    
我是极致的1px

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

Rem的映射方式

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

rem转化公式.png

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

Rem的一些问题

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

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

你可能感兴趣的:(谈谈 rem 与 vw -- rem)