认识 rem

rem(font size of the root element)是相对长度单位。 相对于根元素(即 html 元素)的 font-size 计算值的大小。

  1. // 假如 html 的 font-size 等于 50px,则 1 rem = 50px
  2. .img {
  3. width: 4rem; // 计算后等于 200px
  4. }

适配原理:将 px 替换成 rem,通过动态修改 html 的字体大小实现不同屏幕的适配。

适配处理

以 750px 的设计稿,iPhone 6 为例:

  1. // 1、设置视口
  2. <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  3. // 2、动态设置视口
  4. // 乘以 100 是为了便于计算,例如某张图片为 132px,则(132 / 100) * 2 = 2.64rem
  5. var tid
  6. var docEl = document.documentElement
  7. var refreshRem = function () {
  8. var clientWidth = docEl.clientWidth < 750 ? docEl.clientWidth : 750
  9. var rem = clientWidth / 750
  10. docEl.style.fontSize = (rem * 100) + 'px' // iPhone 6 下 html 的字体大小为 50px
  11. }
  12. // 监听浏览器窗口变化
  13. window.addEventListener('resize', function () {
  14. clearTimeout(tid)
  15. tid = setTimeout(refreshRem, 300)
  16. }, false)
  17. refreshRem()

大家打开 Google 浏览器切换到手机模式并选择 iPhone 6 会发现页面的宽度为 375px,是不是有点子疑惑?iPhone 6 的分辨率不是 750 吗?
截屏2021-09-24 下午4.20.49.png
为了回答上面的问题,我们需要来了解几个基本概念:

  • 设备独立像素(density-independent pixel)
  • 物理像素(physical pixel)
  • 设备像素比(device pixel ratio)
  • CSS像素

设备独立像素(单位:points):又称为密度无关像素,可以认为是计算机坐标系统中的一个点,这个点代表一个可以由程序使用的虚拟像素(比如说CSS像素),然后由相关系统转换为物理像素。

物理像素:又称为设备像素,是设备能控制显示的最小单位,我们可以把它看做显示器上的一个点。(我们常说的分辨率如: 1920x1080 、750 × 1334 就是指物理像素)

设备像素比:简称为dpr,它指的是物理像素和设备独立像素的比值。可以按下面的公式计算得到:

  1. // 在JavaScript中,可以通过 window.devicePixelRatio 获取到当前设备的 dpr
  2. 设备像素比 物理像素 / 设备独立像素

CSS像素:CSS像素是一个抽像的单位,实际上不存在。主要使用在浏览器上,用来精确度量 Web 页面上的内容。

缩合上述的几个概念,我们来理一理它们之间的关系:
image.png
由上图我们可以得出,在不同的屏幕上,CSS像素所呈现的物理尺寸是一致的,而不同的是 CSS 像素所对应的物理像素是不一致的。在普通屏幕下 1 个 CSS像素 对应 1 个物理像素,而在 Retina(dpr=2)屏幕下,1 个CSS像素 对应的却是 4个物理像素。

小结

  1. 在浏览器页面中,CSS 像素的大小和设备独立像素是一样的,只是单位不一样而已。
  2. 普通屏幕下:如 1080P 显示器的分辨率为 1920 ×1080,dpr为 1,则设备独立像素为 1920 ×1080。
  3. Retina 屏:如 iPhone 6 的分辨率为 750 × 1334,dpr为 2,则设备独立像素为 375 × 667。

这下大家理解为什么 F12 审查元素的时候 ,在 iPhone 6 模式下页面的宽度为 375px 了吧,因为它的设备独立像素就是 375 pt 啊。

深入了解 viewport

先思考一个问题:如果上面代码不设置 viewport,页面宽度还会是 375px 嘛?
我们把代码注释掉之后看一下效果:

截屏2021-10-30 下午3.54.29.png

根据测试我们发现,页面宽度为 980px,且浏览器并没有出现横向滚动条,而是默认的把页面缩小了。这是怎么回事呢?为了解答这个问题,我们需要了解:

  1. 什么是 viewport
  2. 布局视口(layout viewport) 和 视觉视口(visual viewport)有什么区别和联系
  3. 如何使用 meta 标签设置 viewport

    什么是 viewport

    通俗的讲, viewport 就是浏览器上显示网页的那部分区域。在默认情况下,移动设备上的 viewport 都是要大于浏览器可视区域的,这是因为考虑到移动设备的分辨率相对于桌面电脑来说都比较小,所以为了能在移动设备上正常显示那些为桌面浏览器设计的网站,移动设备上的浏览器都会把自己默认的 viewport 设为 980px (以 IOS 为例),这个默认的 viewport 叫做 layout viewport。

    layout viewport 不局限于浏览器可视区域的大小,它可能比浏览器的可视区域要大,也可能比浏览器的可视区域要小。所以需要一个 viewport 来代表浏览器可视区域的大小,这个 viewport 叫做 visual viewport。 ```javascript // layout viewport 宽度可以通过 document.documentElement.clientWidth 获取 // visual viewport 宽度可以通过 window.visualViewport 获取

上面的例子中 document.documentElement.clientWidth // 980px window.visualViewport.width // 980.0000610351562px (scale: 0.38265305757522583)

  1. <a name="UVEQU"></a>
  2. ### layout viewport 和 visual viewport 有什么区别和联系
  3. visual viewport 就像一台摄像机,layout viewport 就像一张纸,摄像机对准纸的哪个部分,你就能看见哪个部分。你可以改变摄像机的拍摄区域大小(调整浏览器窗口大小),也可以调整摄像机的距离(调整缩放比例),这些方法都可以改变 visual viewport,但是 layout viewport 始终不变。
  4. <a name="xJVw7"></a>
  5. ### 如何使用 meta 标签设置 viewport
  6. meta viewport 标签首先是由苹果公司在其 safari 浏览器中引入的,目的就是解决移动设备的 viewport 问题。后来安卓以及各大浏览器厂商也都纷纷效仿,引入对 meta viewport 的支持。<br />在苹果的规范中,meta viewport 有6个属性(暂且把content中的那些东西称为一个个属性和值),如下:
  7. | width | 设置_**layout viewport**_ 的宽度,为一个正整数,或字符串 "width-device" |
  8. | --- | --- |
  9. | initial-scale | 设置页面的初始缩放值,为一个数字,可以带小数 |
  10. | minimum-scale | 允许用户的最小缩放值,为一个数字,可以带小数 |
  11. | maximum-scale | 允许用户的最大缩放值,为一个数字,可以带小数 |
  12. | height | 设置_**layout viewport**_ 的高度,这个属性对我们并不重要,很少使用 |
  13. | user-scalable | 是否允许用户进行缩放,值为"no"或"yes", no 代表不允许,yes代表允许 |
  14. 如何把当前的 layout viewport 宽度设置为移动设备的宽度:
  15. ```html
  16. <meta name="viewport" content="width=device-width">

其实还有一句代码也能达到相同的效果:

  1. <meta name="viewport" content="initial-scale=1">

从理论上来讲,这句代码的作用只是不对当前的页面进行缩放,也就是页面本该是多大就是多大。那为什么会有 width=device-width 的效果呢?
这是因为是单独设置 initial-scale 时,浏览器会把 layout viewport 的宽度设置为 visual viewport 的宽度 。而此时初始缩放值为 1,visual viewport 等于移动设备的宽度,即 375px。(可以试试把 initial-scale 设置为其它值看看效果)

如果 width 和 initial-scale=1 同时出现,并且还出现了冲突呢?比如:

  1. <meta name="viewport" content="width=400, initial-scale=1">

当遇到这种情况时,浏览器会取它们两个中较大的那个值。例如,当 width = 400,设备的宽度为 375 时,取的是 400。

小结

  1. layout viewport 时页面渲染的大小,移动设备上默认 980px (以 IOS 为例)。我们可以通过 viewport 设置 width=xx 来改变 layout viewport 的大小。
  2. 调整缩放比例改变的是 visual layout 的大小,缩小其实相当于visual layout 离 layout viewport 更远了,以便能看到更多的内容。而 visual layout 反而更大了,因为它离你更近了。(近大远小)
  3. width=device-width 和 initial-scale=1 同时设置是解决早期部分移动端浏览器横竖屏不分,layout viewport 的宽度总是等于竖屏宽度的问题。

    总结

    单纯为PC端设计的网站不需要设置 viewport,移动端浏览器会默认取较宽的 layout viewport 和 缩放屏幕去展示网站内容。

移动端网站需要设置下面的 viewport,并通过动态设置 rem 来实现不同设备的适配(也有其他方案)。

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

参考资源

viewport 深入理解
VisualViewport
iPhone 分辨率的终极指南
使用Flexible实现手淘H5页面的终端适配