一次搞懂 rem 布局

2021/01/02 一次搞懂系列 CSS 共 5149 字,约 15 分钟
FEHub

rem 弹性布局目前仍是移动端适配的主流方案,以前觉得 rem 布局就是等比缩放,很简单,然而真正把它讲明白却很难。写这篇文章的过程也是一次重新学习的过程。

前言

在进入正题之前,首先认识几个名词,便于后续理解。

  • 像素

    像素是指组成图像的小方块,是整个图像中不可分割的最小单位,每个小方块都有一个明确的位置和被分配的色彩值。

  • 分辨率

    分辨率是指每一个方向上的像素数量,比如我们常说的 1920×1080 分辨率,就是指横向有 1920 个像素,竖向有 1080 个像素。描述分辨率的单位有:

    • dpi - dots per inch,设备分辨率,又称输出分辨率,指的是设备每英寸上可产生的点数。
    • ppi - pixels per inch,图像分辨率,又称像素密度单位,指的是每英寸所拥有的像素数量。
    • lpi - lines per inch,网屏分辨率,线每英寸。
    • ppd - pixels per degree,角分辨率,像素每度。
  • 物理分辨率

    物理分辨率是指液晶屏最高可显示的像素数,它是液晶屏固有的参数不能调节,表示方法与分辨率相同。一般来讲物理分辨率的大小可直接决定屏幕的最高分辨率,分辨率不会大于物理分辨率。

    比如说,一款物理分辨率为 1366x768 的液晶电视,号称兼容 1080P 的信号,其实是 1080P 的信号电视可以接收,但输出时只会按照液晶电视 1366x768 的物理分辨率进行输出。只有物理分辨率达到 1920x1080 的液晶电视才能支持 1080P 的信号输出。

  • 设备像素 dp (Device Pixel)

    物理像素,是指设备能控制显示的最小单位,我们常说的显示器 1920×1080 分辨率就是设备像素。

  • 设备独立像素 dip (Device Independent Pixel)

    逻辑像素,是指可以通过程序控制使用的虚拟像素,与设备无关,包括了 CSS 像素 px,iOS 的 pt,Android 的 dp 等。

    • CSS 像素

      CSS 像素是 Web 编程的概念,指的是 CSS 样式代码中使用的逻辑像素,是一个抽象概念,实际并不存在。

    • pt

      pt 是苹果自定义的设计单位(不是绝对长度为 1/72 英寸的 pt)。第一代 iPhone 3G 的屏幕是 320x480 分辨率,163ppi,苹果规定在 163ppi 的时候 1pt = 1px,所以 1pt = (ppi/163)px(这里的 px 是屏幕的物理像素)。

      比如 iPhone 6 是 326ppi,那么 1pt = (326/163)px = 2px。iPhone 6 Plus 是 401ppi,计算得出 1pt = 2.46px,而实际是 3px,因为在图像呈现在屏幕之前做了一次采样处理。

    • dp

      和苹果的 pt 一样,dp 是安卓为了设计而创造的独立单位,1dp = (ppi/160)px。

    • pt、dp 与 px 的关系

      如果在 HTML head 中设置了 <meta name="viewport" content="width=device-width,initial-scale=1">,此时 CSS 像素的显示尺寸和 Native APP 中 pt、dp 的显示尺寸相等。

  • devicePixelRatio (dpr)

    设备像素 dp 和设备独立像素 dip 的比例,也就是 dpr = dp / dip,在浏览器中可以通过 window.devicePixelRatio 获取到对应设备的 dpr。

举个例子🌰:

iPhone 6 的屏幕参数是,4.7英寸(对角线)Retina 高清显示屏,750x1334 分辨率,326ppi。iPhone 6 的横向分辨率(设备像素)是 750px,dpr(devicePixelRatio)是 2,所以它横向的 CSS 像素(设备独立像素)为 375px。

image

进入正题

CSS 中有两种类型的长度,绝对长度和相对长度,绝对长度有我们常见的 px,pt(point)等,而 rem 是一个相对长度,另外还有 em,vw,vh 这些都是相对长度,这里不再赘述,具体请参考 CSS 的值与单位

rem 单位的意思是“根元素的字体大小”,即 html 元素的 font-size 是多少 px,1rem 就是多少 px。

如果我们按设计稿的尺寸用 px 为单位实现了一个页面,理论上只在设计稿对应分辨率的屏幕上看才是正好的,在更大或者更小的屏幕上看可能就不合适了,这时候我们就要用到 rem 布局通过放大或者缩小让页面适应当前的屏幕。

rem 布局是弹性布局的一种实现方式,弹性布局可以算作响应式布局的一种,但响应式布局不全是弹性布局。弹性布局强调等比缩放,100% 还原,rem 布局的本质就是等比缩放;响应式布局强调不同屏幕要有不同的显示,比如媒体查询。

rem 布局原理

以设计稿的尺寸(一般是宽度)为标准,我们定义一个数值代表此时的 1rem 是多少,因为 rem 的本质是等比缩放,我们可以得到:

设计稿宽度 / 设计稿的 1rem = 页面视口宽度 / 页面的 1rem

这里的视口是指布局视口(layout viewport),在浏览器中可以通过 window.innerWidthdocument.documentElement.clientWidth 获取视口宽度。具体请参考 视口概念

然后我们可以计算出不同设备上页面对应的 1rem 的值:

页面的 1rem = 页面视口宽度 / (设计稿宽度 / 设计稿的 1rem)

其中,设计稿宽度 / 设计稿的 1rem 相当于以 1rem 为基准将设计稿平均分成了若干份,那么上边的式子我们可以理解为:把不同设备上页面的视口平均分成和设计稿相同的份数,每一份就是 1rem。

接下来,在浏览器渲染页面阶段,加载 CSS 之前,我们通过 document.documentElement.clientWidth 获取到当前设备上页面视口的宽度(CSS 像素),根据上面的公式动态计算出根元素的 font-size 值,然后通过 JS 动态把值赋给 html 元素。

document.documentElement.style.fontSize = document.documentElement.clientWidth / 7.5 + 'px';

在解析 CSS 时,浏览器会根据 html 元素的 font-size 值把 rem 值换算成适合自己的 px 值,这样就实现了使用一套 CSS 代码适配不同的设备。

举个例子🌰:

设计师按照 750px 的标准做设计稿,为了方便计算我们取 100px 为 1rem,那么设计稿的宽度就是 7.5rem,不同设备上页面对应的 1rem 的值就是页面视口宽度 / 7.5。iPhone6 横向的 CSS 像素为 375px,那么对应的 1rem 数值就是 375/7.5 = 50px。

注意事项

  • 在实现 CSS 代码时,我们可以提前计算好 rem 值写在代码中,也可以直接按设计稿标注写 px,然后通过 CSS 处理器去转换,但要注意配置好 px2rem 的比例。

  • HTML 中要对视口做如下设置:

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

有关 <meta name="viewport"> content 属性的值,可以参考 标准元数据名称

提出疑问

到这里我们好像已经实现了使用 rem 布局来适配不同的设备,但是感觉还有点疑问。

设计师:设计稿是按照 iPhone 6 横向分辨率 750px 的标准给出的。

我:秋豆麻袋,iPhone 6 横向的 CSS 像素为 375px,所以说上边的例子我们实现的其实是两倍的 iPhone 6 的视图,应用在页面上时,首先经过了一次缩小的过程。总感觉怪怪的。

上边我们在 HTML 中设置了 viewport,其中的 scale 表示缩放比例,width=device-width 表示整个页面在设备上显示时的视口宽度(布局视口,CSS 像素)等于设备屏幕宽度(CSS 像素)。此时页面视口宽度的计算公式为:

页面的视口宽度 = 设备的物理分辨率 / (devicePixelRatio * scale)

如果 viewport 的 scale 根据 devicePixelRatio 动态设置:

  • 当 devicePixelRatio 为 1 时,scale 为 1。
  • 当 devicePixelRatio 为 2 时,scale 为 0.5。
  • 当 devicePixelRatio 为 3 时,scale 为 0.3333。

也就是说,devicePixelRatio * scale 始终等于 1,那么页面的视口宽度就和设备的物理分辨率始终相等。iPhone 6 的 devicePixelRatio 为 2,当 scale 为 0.5 时,iPhone 6 显示的页面视口宽度就和设计稿一样都是 750px 了。

image

网易的做法

网易使用了我们上边说的 scale 固定为 1 的方法。以 iPhone 6 的 750px 设计稿为例:

  • 为了方便计算,以 100px 的 font-size 为参照,此时 1rem = 100px。

  • 计算设计稿的宽度为 750 / 100 = 7.5rem。

  • 通过以下代码动态设置 html 元素的 font-size:

document.documentElement.style.fontSize = document.documentElement.clientWidth / 7.5 + 'px';
  • 写 CSS 代码时,各元素的 CSS 尺寸(rem 值)= 设计稿标注尺寸 / 100。

淘宝的做法

淘宝使用了动态设置 scale 的方法,如上述所说根据设备的 devicePixelRatio 动态设置 scale 的值,使页面的视口宽度和设备的物理分辨率始终相等。还是以 iPhone 6 的 750px 设计稿为例:

  • 设定设计稿的宽度为 10rem。

  • 此时 html 元素的 font-size 值为 750 / 10 = 75px,即 1rem = 75px。

  • 动态设置 viewport 的 scale:

var scale = 1 / devicePixelRatio;
document.querySelector('meta[name="viewport"]').setAttribute('content','initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
  • 动态计算 html 元素的 font-size:
document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px';
  • 写 CSS 代码时,各元素的 CSS 尺寸(rem 值)= 设计稿标注尺寸 / 75。

举一反三

移动端和 PC 端的临界点

通常情况下,移动端和 PC 端的页面不是同一种展示方式。如果我们按 iPhone 6 的标准实现页面,那么在 iPhone 6/7/8 Plus 等更大的屏幕上页面会等比放大,但当屏幕继续增大时,比如 iPad/PC 等,我们就没必要让它继续放大了,此时应该去访问 PC 网站。比如我们用手机访问百度看到的是移动端的页面,用 iPad 或者 PC 访问百度看到的则是电脑端的页面。

当页面的视口宽度大于某个值时,就可以让 html 元素的 font-size 不再变化了。动态设置 html 元素 font-size 的代码改动一下:

var deviceWidth = document.documentElement.clientWidth;
// 980 只是举例,具体数值按需求确定
if(deviceWidth > 980) deviceWidth = 980;
document.documentElement.style.fontSize = deviceWidth / 7.5 + 'px';

工作中与设计师协作

  • 视觉设计阶段,设计师按 750px 分辨率(iPhone 6/X)做设计稿。设计定稿后在 750px 的设计稿上做标注,输出标注图。同时等比放大 1.5 倍生成宽度 1125px 的设计稿,在 1125px 的稿子里切图。

  • 设计师输出宽度 750px 的设计标注图和 @2x/@3x 切图资源交付给前端开发工程师。

  • 开发工程师拿到标注图和切图资源,完成 iPhone 6/X(375pt)的界面开发。开发阶段用我们上面所说的 rem 布局实现。

  • 适配调试阶段,基于 iPhone 6/X 的界面效果,分别向上向下调试 iPhone 6/7/8 plus / iPhone 11(414pt),iPhone 12(390pt)和 iPhone 5S 及以下(320pt)的界面效果,由此完成大中小三屏适配。

以上。

参考资料

文档信息

Search

    Table of Contents