移动端适配总结

2,012 阅读48分钟

关于移动端适配,你必须要知道的 这篇文章对于移动端适配相关内容总结的非常好,但对于视口相关内容不容易理解,所以加入个人理解与实践。

移动端适配,是我们在开发中经常会遇到的,这里面可能会遇到非常多的问题:

  • 1px 问题
  • UI 图完美适配方案
  • iPhoneX 适配方案
  • 横屏适配
  • 高清屏图片模糊问题
  • ...

上面这些问题可能我们在开发中已经知道如何解决,但是问题产生的原理,以及解决方案的原理可能会模糊不清。在解决这些问题的过程中,我们往往会遇到非常多的概念:像素、分辨率、PPIDPIDPDIPDPR、视口等等。

英寸

一般用英寸描述屏幕的物理大小,如电脑显示器的 1722,手机显示器的 4.85.7 等使用的单位都是英寸。

需要注意,上面的尺寸都是屏幕对角线的长度:

英寸(inch,缩写为 in)在荷兰语中的本意是大拇指,一英寸就是指甲底部普通人拇指的宽度。

英寸和厘米的换算:1英寸 = 2.54 厘米

分辨率

通常我们所说的分辨率有两种,屏幕分辨率图像分辨率,它们都是由像素点组成。

像素点

像素,即一个小方块,它具有位置和颜色两个属性。电子屏幕(手机、电脑)就是由多个具有特定位置和颜色的方块拼接而成。

像素可以作为图片或电子屏幕的最小组成单位,下面我们使用 sketch 打开一张图片:

将这些图片放大即可看到这些像素点:

屏幕分辨率

屏幕分辨率指一个屏幕具体由多少个像素点组成。下面是 apple 的官网上对手机分辨率的描述:

iPhone XS MaxiPhone SE 的分辨率分别为 2688 x 12421136 x 640。这表示手机分别在垂直和水平上所具有的像素点数。

当然分辨率高不代表屏幕就清晰,屏幕的清晰程度还与尺寸(上面的英寸)有关,它衍生出了 PPIDPI 两个概念。

图像分辨率

我们通常说的 图片分辨率 其实是指图片含有的 像素数,比如一张图片的分辨率为 800 x 400。这表示图片分别在垂直和水平上所具有的像素点数为 800400

同一尺寸的图片,分辨率越高,图片越清晰。

像素密度 PPI

PPI(Pixel Per Inch):每英寸包括的像素点数,可以用于描述屏幕的清晰度以及一张图片的质量。

  1. 使用 PPI 描述【图片】时,PPI 越高,图片质量越高,使用 PPI 描述屏幕时,PPI 越高,屏幕越清晰。

  2. 使用 PPI 描述【屏幕】时,我们可以看到:iPhone XS MaxiPhone SEPPI 分别为 458326,这足以证明前者的屏幕更清晰。

由于手机尺寸为手机对角线的长度,我们通常使用如下的方法计算 PPI

水平像素点2+垂直像素点2对角线英寸\frac{\sqrt{水平像素点数^2+垂直像素点数^2}}{对角线英寸}

iPhone 6PPI326 个像素点。

打印密度 DPI

DPI(Dot Per Inch):即每英寸包括的点数,而点数表示的是像素转化为现实世界物理意义上的大小尺寸,主要指打印机的墨点,即每英寸在打印机上的墨点数。

一张图片在屏幕上显示时,它的像素点数是规则排列的,每个像素点都有特定的位置和颜色。

当使用打印机进行打印时,打印机可能不会规则的将这些点打印出来,而是使用一个个打印点来呈现这张图像,这些打印点之间会有一定的空隙,这就是 DPI 所描述的:打印点的密度。

在上面的图像中我们可以清晰的看到,打印机是如何使用墨点来打印一张图像。所以,打印机的 DPI 越高,打印图像的精细程度就越高,同时这也会消耗更多的墨点和时间。

设备像素

设备物理像素

实际上,上面我们描述的像素都是 物理像素,即设备上真实的物理发光单元,也称 设备物理像素。下面我们来看看 设备独立像素(Device Independent Pixels,简称 DIP 或者 DP) 究竟是如何产生的:

设备独立像素 DIP

智能手机发展非常之快,在几年之前,我们还用着分辨率非常低的手机,比如下面左侧的白色手机,它的分辨率是 320x480(设备物理像素),我们可以在上面浏览正常的文字、图片等等。

但是,随着科技的发展,低分辨率的手机已经不能满足我们的需求了。很快,更高分辨率的屏幕诞生了,比如下面的黑色手机,它的分辨率是 640x940(设备物理像素),正好是白色手机的两倍。

理论上来讲,在白色手机上相同大小的图片和文字,在黑色手机上会被缩放一倍,因为它的分辨率提高了一倍。这样,岂不是后面出现更高分辨率的手机,页面元素会变得越来越小吗?

然而,事实并不是这样的,我们现在使用的智能手机,不管分辨率多高,他们所展示的界面比例都是基本类似的。乔布斯在 iPhone4 的发布会上首次提出了 Retina Display( 视网膜屏幕)的概念,它正是解决了上面的问题,这也使它成为一款跨时代的手机。

iPhone4 使用的视网膜屏幕中,把 2x2 个像素当 1 个像素使用,这样让屏幕看起来更精致,但是元素的大小却不会改变。

如果黑色手机使用了视网膜屏幕的技术,那么显示结果应该是下面的情况,比如列表的宽度为 300 个像素,那么在一条水平线上,白色手机会用 300 个物理像素去渲染它,而黑色手机实际上会用 600 个物理像素去渲染它。

我们必须用一种单位来同时告诉不同分辨率的手机,它们在界面上显示元素的大小是多少,这个单位就是设备独立像素Device Independent Pixels)简称 DIP 或者 DP 。上面我们说,列表的宽度为 300 个像素,实际上我们可以说:列表的宽度为 300 个设备独立像素。

在设计那边喜欢称呼 DIP 为设备逻辑像素,意思相同,用来区别设备物理像素

打开 chrome 的开发者工具,我们可以模拟各个手机型号的显示情况,每种型号上面会显示一个尺寸,比如 iPhone X 显示的尺寸是 375x812,实际 iPhone X 的分辨率会比这高很多,这里显示的就是设备独立像素。

设备像素比 DPR

设备像素比 Device Pixel Ratio 简称 DPR,即设备物理像素和设备独立像素的比值。

  1. 在 Web 中,浏览器为我们提供了 window.devicePixelRatio 来帮助我们获取 DPR

  2. 在 CSS 中,可以使用媒体查询 min-device-pixel-ratio,区分 DPR

    @media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2){ }
    
  3. React Native 中,我们也可以使用 PixelRatio.get() 来获取 DPR

当然,上面的规则也有例外,iPhone 6、7、8 Plus 的实际物理像素是 1080 x 1920,在开发者工具中我们可以看到:它的设备独立像素是 414 x 736,设备像素比为 3,设备独立像素和设备物理像素比的乘积并不等于 1080 x 1920,而是等于 1242 x 2208

实际上,手机会自动把 1242 x 2208 个像素点塞进 1080 * 1920 个物理像素点来渲染,我们不用关心这个过程,而 1242 x 2208 被称为屏幕的 设计像素。我们开发过程中也是以这个 设计像素 为准。

实际上,从苹果提出视网膜屏幕开始,才出现设备像素比这个概念,因为在这之前,移动设备都是直接使用物理像素来进行展示。

紧接着,Android 同样使用了其他的技术方案来实现 DPR 大于 1 的屏幕,不过原理是类似的。由于 Android 屏幕尺寸非常多、分辨率高低跨度非常大,不像苹果只有它自己的几款固定设备、尺寸。所以,为了保证各种设备的显示效果,Android 按照设备的像素密度将设备分成了几个区间:

当然,所有的 Android 设备不一定严格按照上面的分辨率,每个类型可能对应几种不同分辨率,所以,每个 Android 手机都能根据给定的区间范围,确定自己的 DPR,从而拥有类似的显示。当然,仅仅是类似,由于各个设备的尺寸、分辨率上的差异,设备独立像素也不会完全相等,所以各种 Android 设备仍然不能做到在展示上完全相等。

CSS 像素单位 PX

CSS 像素是一种虚拟像素,可以理解为“直觉”像素,随着 设备独立像素缩放行为 可大可小的。浏览器内的一切长度都是以 CSS 像素为单位的,CSS 像素的单位是 px

在 CSS 规范中,长度单位可以分为两类,绝对(absolute)单位以及相对(relative)单位。px 是一个相对单位,1 px 相对的是 一个 设备物理像素。

在上一小节中,我们已经知道了在移动端浏览器中以及某些桌面浏览器中,window 对象有一个 Device Pixel Ratio( 设备像素比 DPR)属性,而它的官方的定义为:设备物理像素和设备独立像素的比例,从而我们可以得到下面公式:

  1. DPR(设备像素比)= 设备物理像素 / DIP(设备独立像素) ,而 px 相对的是设备物理像素,所以公式也可以是 DPR = PX / DIP
  2. 没有浏览器缩放或者缩放为 100% 情况下:PX = DIP * DPR ,当一个设备 DPR 为 2 时,例如在宽度上,我们都知道 iphone6 的设备物理像素为 750px,设备独立像素为 375px,设备像素比为 2,这些都是厂家固定设置不变的。所以根据公式 iphone6 上元素 1 PX = 2 个设备独立像素单位。而 iphoneX 的只看设备像素比就知道 1 PX = 3 个设备独立像素单位,由此可见 CSS 像素单位 PX 在移动端是动态变化的,也正如此一套 CSS 代码才能实现差不多尺寸手机,不同的屏幕分辨率,显示差不多的效果

    举个例子,iphone6 上一个 icon 图标,设计师交付的 UI 设计稿可能有两种区别,想要显示效果正确:

    1. 设计师交付的设计稿,如果总宽度是 750px,那么使用的设备【物理像素】标准。图标在设计稿中大小为 32px,在那么我们书写 css 时的单位就要除 2 = 16px。

    2. 设计师交付的设计稿,如果总宽度是 375px,那么使用的设备【独立像素】标准。图标在设计稿中大小就应该为 16px,在那么我们书写 css 时的单位时直接写 16px,设计师帮我们做了像素比的换算。

显示缩放行为

界面的显示效果除了受【固定的设备独立像素】的影响之外,还有一个非常非常重要的因素影响,即用户操作或者自动的【显示缩放】行为。

系统与页面缩放

可知的显示缩放行为有三种:

  1. 苹果独有的【Retina】缩放,其带来了设备独立像素概念,可以理解成【独特的操作系统底层“缩放”】
    • 被缩放的是【设备物理像素】
    • 效果:控制多个设备物理像素合并显示为一个【设备独立像素】
    • 特征:系统底层设置好,不需要用户操作
  2. 操作系统中的【系统显示缩放】,用户可操作缩放行为,与 Retina 差不多效果。
    • 被缩放的是【设备物理像素】
    • 效果:控制多个设备物理像素合并显示为一个【设备独立像素】
    • 特征:桌面端系统需要手动设置,默认不缩放,移动端系统底层都设置好了缩放
  3. 浏览器内部窗口的【页面显示缩放】,
    • 被缩放的是【CSS 像素】,是一种虚拟像素,单位是 PX
    • 效果:控制 CSS 像素占用多少设备独立像素
    • 特征:随着用户放大缩小,CSS 像素的占用设备独立像素跨度随时变化,然后系统显示缩放再向下转换成占用多少设备物理像素。换一种理解就是页面显示缩放是在系统显示缩放的基础之上进行的。

CSS 像素缩放

CSS 像素在视觉上是很容易改变大小的,当缩放浏览器页面时,就是改变的 CSS 像素,当放大一倍,那么一个 CSS 像素在横向或者纵向上会覆盖两个设备独立像素。例如宽度 100px,当页面放大一倍,它会在横向上由原本占据 100 个设备独立像素,变成占据 200 个设备独立像素;如果缩小,则恰好相反,只能占据 50 个设备独立像素。

图示说明:

  1. 设备独立像素(深蓝色背景)、CSS 像素(半透明背景)。
  2. 左图表示当用户进行缩小操作时,一个设备独立像素覆盖了多个 CSS 像素。
  3. 右图表示当用户进行放大操作时,一个 CSS 像素覆盖了多个设备独立像素。

无论 CSS 像素是缩小还是放大,它是像素数目是不变的,比如 100px,无论缩放,它依然是 100px,只不过它占据的设备独立像素发生了变化(体积发生了变化,视觉大小上发生了变化而已)。

桌面端浏览器

PC 端和 MAC 电脑等桌面端越来越多使用高分辨率屏幕,如 4K 显示器不缩放的话,在笔记本电脑上,十几寸的物理大小,字体太小了,也就出现了上文移动端的问题,当然苹果的 Retina 技术早用到 mac book 上。PC 电脑只是用户手动操作的【系统显示缩放】,来放大内容显示效果。

  1. 当用户系统不进行缩放时,比如超大尺寸的 4K 显示器,也没必要缩放,系统程序 GUI 和字体的 1 个像素点 = 1 设备物理像素 = 1 个设备独立像素 = 1px 网页 css 像素单位

  2. 当用户系统缩放系数为 200% 时,比如小尺寸的笔记本显示器,系统程序 GUI 和字体的 1 个像素点 = 4 设备物理像素 = 1 个设备独立像素 = 1px 网页 css 像素单位

一般在 Web 开发的网页中,如果用户没有手动缩放行为,CSS 像素 的单位 1px = 1 个设备独立像素。这就是为什么前端书写桌面端 CSS 不需要关心适配问题,PX 单位直接等于设备独立像素,天然适配系统的各种缩放。

上面讨论的是 系统显示缩放 或者 Reatina,表示将【设备物理像素】通过缩放转换为【设备独立像素】,还有用户的缩放行为,表示将【CSS 像素 PX】通过缩放转换占用多少【设备独立像素】,这也证明了 CSS 像素是一种虚拟像素,可大可小。

下面为用户放大和缩小演示:

  1. 当用户手动将网页显示缩小为 25%(最小的手动缩小系数),页面的 CSS 像素的单位 1px 等于 0.25 个设备独立像素,下图中 html 根元素为 3964px * 0.25 = 991 个设备独立像素宽度 = 浏览器当前窗口的宽度
  2. 当用户手动将网页显示放大为 500%(最大的手动放大系数),页面的 CSS 像素的单位 1px 等于 5 个设备独立像素,下图中 html 根元素为 372.8px * 5 = 1864 个设备独立像素宽度 = 浏览器当前窗口的宽度

需要分清楚的是,html 根元素的宽度可能并不是网页的最大内容宽度,当我们给浏览器当前窗口大小设置为 1093 个设备独立像素

<!DOCTYPE html>
<html lang="en">
  <head>
    <style>
      .ccc {
        width: 3000px;
        background-color: red;
        font-size: 20px;
      }
    </style>
  </head>
  <body>
    <div class="ccc">我的宽度为 3000px 像素</div>
  </body>
</html>

所以桌面端浏览器还有个默认行为:当网页内容宽度超出浏览器窗口大小时,出现横向滚动条

移动端浏览器

早期时候,Web 还是以 PC 端网站为主,慢慢智能手机出现,内置移动端浏览器可以上网看 PC 端网页内容。

因为手机的应用一般都是全屏的,所以移动端浏览器的窗口宽度就等于是手机的屏幕宽度,即浏览器窗口宽度大小在移动端上是固定不变的。早期的手机没有 Retina 类式"缩放"技术,1 个设备物理像素 = 1 个设备独立像素 = 1px 浏览器中的 CSS 像素,后来出现了 Retina 技术,(设备像素比 DPR * 1)个设备物理像素 = 1 个设备独立像素 = 1px 浏览器中的 CSS 像素,与桌面端浏览器同样原理,我们只需要知道在浏览器上,没有发生 CSS 像素缩放时:1 个设备独立像素 = 1px CSS 虚拟像素 即可。

实际操作中发现,手机的屏幕是在太窄了,即设备物理/独立像素宽度都很小,为了在移动端浏览器上观看 PC 端的网页内容,各大浏览器开始操控自动缩小 CSS 像素(与桌面端浏览器里用户手动缩放页面显示一个意思,只是移动端上变成了默认行为),打破 1 个设备独立像素 = 1px CSS 虚拟像素的默认平衡,让 1 个设备物理/独立像素显示更多的 CSS 内容,最终得以较为完整的在手机上显示 pc 网页内容。

注意,演示上文的时候,网页文档不要使用 Meta Viewport 标签,它是另一种解决方案,不能共存。

既然是自动缩小 CSS 像素,各开发商得有个统一标准,所以后来出现了一个约定的默认标准 980px ,在手机上浏览器上,不管当前的网页的真实内容宽度是多少,一律将当前浏览器窗口宽度所转换的 CSS 像素设置为 980px 固定值,网页内容超出就有横向滚动条,不再自动缩小。

那个时期 PC 端网页的普遍宽度比较小,980px 比较接近,差不多恰好在手机上显示全 pc 网站,偶尔少量横向滚动条。该方案可能是 apple 公司推出的

虽然 PC 端网页可以通过上面方式在移动端显示,但是体验不佳,内容拥挤狭小,需要用户手指放大内容查看。

正确的做法应该是进行自适应或者响应式的网站重构,为移动端专门开发适配的内容,乔帮主(🐂)也给出了另一套方案 Meta Viewport 视口。

视口 Viewport

视口(viewport) 代表当前【可见】的计算机图形区域。在 Web 浏览器术语中,视口与浏览器窗口基本相同,但不包括浏览器的 UI, 菜单栏、地址栏等。

视口 Viewport 特性,一个移动端专属的 Meta 值,用于定义视口的各种行为。该特性最先由 Apple 引入,用于解决移动端浏览器展示 pc 端网页问题,后续被越来越多的厂商跟进。

apple 公司提出的两个方案,浏览器窗口 CSS 像素默认宽度 980px 是用来解决 PC 端网页直接在移动端上显示问题。而视口是另一套解决方案,用来服务专门为移动端开发的网页,决定其如何更好的适配屏幕。

当网页文档拥有 Meta Viewport 标签后,默认 980px 方案就不会启用。

视口viewport)就是浏览器的当前窗口大小的可见区域,通过前文的铺垫,我们应该知道浏览器窗口大小有多种单位表示,如下三种:

  1. 设备物理像素:纯硬件屏幕发光单元个数,一个单元一个物理像素
  2. 设备独立像素:由操作系统的设备像素比来决定多少个物理像素合并为一个独立像素显示
  3. CSS 像素:完全的虚拟像素,按【显示缩放】的比例占据具体多少【设备独立像素】

落实到具体的网页中,W3C 规范决定使用 CSS 像素单位 px 来定义长度,而浏览器用来渲染网页,其当前窗口大小自然可以用 CSS 像素单位 px 来表示,并且 html 根元素就是依据当前窗口大小的 CSS 像素单位来计算的

又因为 CSS 像素单位可缩放的特性,就算浏览器窗口大小(设备独立像素)没有变化,如桌面端浏览器没有拖动窗口,如移动端浏览器全屏下当前窗口,CSS 像素单位 PX 具体值都是可以通过缩放随意变化的,进而影响网页整体在手机上显示大小。

除了用户手动缩放 CSS 像素(网页显示大小)与移动端浏览器默认缩放为 980px 两种之外,还有 Meta Viewport 标签用来操控当前窗口宽度的 CSS 像素的具体值与 CSS 像素缩放系数

很多文章中,引用 ppk 大神的多个视口概念(A table of two viewports):布局视口(layout viewport)、视觉视口(visual viewport)、理想视口(ideal viewport)。我个人是初看厉害,越看越迷糊,反而是增加了理解难度,并且这三个视口都没有出现在任何正式规范中。

Meta Viewport 标签

<meta> 元素表示那些不能由其它 HTML 元相关元素之一表示的任何元数据信息,它可以告诉浏览器如何解析页面。

我们可以借助 <meta> 元素的 viewport 来帮助我们设置视口、缩放等,从而让移动端得到更好的展示效果。

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

上面是 viewport 的一个配置,我们来看看它们的具体含义:

Value可能值描述
width正整数或 device-width以 px 像素为单位, 定义当前视口的宽度
height正整数或 device-height以 px 像素为单位, 定义当前视口的高度
initial-scale0.0 - 10.0定义视口的初始化缩放系数
minimum-scale0.0 - 10.0定义缩放的最小值;必须小于或等于 maximum-scale 的值。
maximum-scale0.0 - 10.0定义缩放的最大值;必须大于或等于 minimum-scale 的值。
user-scalable一个布尔值(yes 或者 no如果设置为 no,用户将不能放大或缩小网页。默认值为 yes。

需要注意,现代浏览器好像无视这一属性,强势让用户可以进行手指缩放,查看

上面的所有属性,最核心的是 widthinitial-scale 属性,其他都是辅助作用。

width 属性

该属性可以设置为正整数和 device-width ,它的作用是设置视口的宽度,通过前文我们知道视口就是浏览器当前窗口大小的 CSS 像素单位表示方式。通过 width 属性的正整数值可以直接指定浏览器的窗口宽度,用 CSS 像素单位 PX 表示。

类式于 ppk 大神提出,width 属性设置的视口是:layout viewport 布局视口。

因为网页文档的 html 根元素内容区宽度继承浏览器当前窗口宽度,所以通过如下属性可以获取浏览器当前窗口宽度 CSS 像素值。

document.documentElement.clientWidth

注意,不包括浏览器的竖向滚动条,html 根元素的 border 边框,margin 外边距等。

initial-scale 属性

该属性是用来定义视口的初始化缩放系数,即浏览器窗口的默认初始化缩放系数(网页渲染后,用户可以手指缩放改变,我们可以做限制不让用户手指缩放),取值范围是 0.0 到 10.0 ,正好对应 0% 到 1000% 的缩放比例。

需要注意,initial-scale100% 缩放下(值为 1),浏览器窗口的 CSS 像素单位 1px = 1 个设备独立像素,其他缩放比例将会改变这个比值。 例如,initial-scale 设置为 2 以后,1px = 2 个设备独立像素。由于缩放行为改变 CSS 像素的转换比例,那么等同于改变当前浏览器的窗口显示效果,因为在手机宽度不变、手机的设备独立像素也是固定不变的情况下,改变比列,只能 CSS 像素总值变大或者变小

通过如下属性可以获得改变后的当前窗口宽度的 CSS 像素值,又因为此 API 是浏览器提供的,并非上面的 html 元素继承获取,所以会包括竖向滚动条。

竖向滚动条在以前是一个问题,现在 2021 年真机上看更像是一个悬浮层,不干扰文档流,即不影响布局,我们可以不用关心它了

window.innerWidth

到这我们应该注意到,initial-scale 属性与 width 属性存在冲突了,它们两实际上都能控制浏览器窗口的 CSS 像素值,即决定了显示效果上的放大和缩小。那么浏览器应该听谁的?

真机检验 initial-scale 缩放

不要使用桌面端浏览器的移动端模式来模拟效果,并不准确,其 initial-scale 缩放值始终是 4 倍,即网页文档设置的 initial-scale 失效了,要用真实手机浏览器

我们上真机检验,以 iphoneX 手机为例,其设备独立像素是 375px 固定不变的,而手机浏览器都是全屏运行,所以 iphoneX 浏览器的窗口宽度,用设备独立像素来表示也是永远固定的 375px。我们假设一个设置 Meta Viewport 标签的网页在浏览器上运行,其属性值为

<meta name="viewport" content="width=300, initial-scale=1" />

上面表示,浏览器当前窗口的宽度 CSS 虚拟像素设置为 300px(width 属性),CSS 缩放为 100%(initial-scale 属性),也就是没有缩放,真机效果如下图

从上图可以得出结论:因为缩放为 100%,即 1px = 1 设备独立像素,总的设备独立像素是 375 个,那么当前浏览器窗口的宽度用 CSS 像素也同样应该是 375px,而 width 属性指定的是 300px,小于 375px,所以 width 属性被自动从 300px 提升到了 375px,用来表示应有的浏览器窗口宽度。

上面是当 initial-scale 属性缩放后的 CSS 像素值大于 width 属性指定的 CSS 像素值的情况。如果小于呢?我们重新设置 Meta Viewport 标签缩放属性:

<meta name="viewport" content="width=300, initial-scale=2"/>

咱先分析一下,因为缩放比例为 200%,此时 1px = 2 个设备独立像素,在独立像素总量不变的情况下,375 / 2 = 187.5px ,即当前浏览器窗口宽度的 CSS 像素值应该为 187.5px。接着在真机上验证:

从上图看,我们发现整体的显示效果确实像放大了一倍的样子,这表示 CSS 像素容纳了更多的设备独立像素,当前浏览器宽度的 CSS 值为 188px(应该是不能用小数表示,187.5 四舍五入为 188px)。还有就是 width 这次并没有自动改变为当前浏览器窗口的 CSS 像素值 188px,仍然为 300px,这就与 width 设定不符了。

所以只有一种可能:因为 initial-scale 相对于设备独立像素来缩放 CSS 像素的行为必须优先级更高,才会有正确的缩放显示效果反馈。当它缩放后的值 大于 width 指定值时,width 自动提升到缩放后的值等同。当它缩放后的值 小于 width 指定的值时,从上图可以看出实际窗口宽度值为缩放后的值,而 width 不再用来指示当前窗口宽度,变成最大的缩小值(指定的是用户手指触摸缩小)。从 iphoneX 真机上看(没有安卓机),确实手指只能缩小到 300px 的范围。

如果 Meta Viewport 标签只设置了 width=300,而没有设置 initial-scale 属性时,initial-scale 怎么表现,或者说默认值是多少?

<meta name="viewport" content="width=300"/>

上图所示,initial-scale 缩放后的 CSS 像素值跟随 width,其缩放了 300(width)/375(当前手机设备独立像素)= 0.8。所以没有添加 initial-scale 属性时,浏览器当前窗口宽度才终于让 width 属性说了算,而innerWidth 的值,跟随 width 一致。

viewport 正确设置

上文的实现分析,是一头乱麻,实际用处也不大,我们可以抛弃这些奇怪的表现。从移动端适配的角度给出最适合的属性设置,达到适配的目的。

Meta Viewport 标签的 width 属性还拥有一个字段属性值 device-width ,该字段的意思是:将浏览器当前窗口宽度的 CSS 值设置为【设备独立像素】的宽度个数,如 iphone5 宽为 320 个设备独立像素(也称设备逻辑像素),即 device-width = 320px,iphoneX 为 device-width = 375px

再让 initial-scale 属性的缩放比例设置为 1,即 100%,没有缩放效果。结合起来如下标签:

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

如上设置实现了:移动端浏览器窗口的 CSS 像素严格等于手机的设备独立像素,即 1px = 1 设备独立像素的效果。我们网页书写的不同 px 单位都会一比一的对应设备独立像素,而【设备独立像素】转换为【多少个设备物理像素】去渲染呈像不需要我们关心,自动实现了不同的移动端浏览器下,显示差不多的效果。

注意为什么是差不多的效果,不是完美呢?因为不同的移动端设备的设备独立像素是不同的,如上图片仔细看。所以当我们的 UI 设计图是使用较小的设备产出时,如常见的 iphoneX 的 375px 做为设计稿,通过 Meta Viewport 标签如上设置会自动适配。但面对 iphoneXR 的 414 设备独立像素,就有了 414 - 375 = 39 个设备独立像素的空白地区,所以下文移动端适配方案(rem 和 vw/vh),就是来解决这个空白区域问题的。

移动端适配方案

flexible/rem 布局

flexible 方案是阿里早期开源的一个移动端适配解决方案,引用 flexible 后,我们在页面上统一使用 rem 来布局。

它的核心代码非常简单:

// 设置 1rem = 当前视口的 10 分之一
function setRemUnit () {
    var rem = document.documentElement.clientWidth / 10
    document.documentElement.style.fontSize = rem + 'px'
}
setRemUnit()

document.documentElement.clientWidth 可以获取 html 根节点的内容区宽度(不包括它自身的边框与外边距),通过前文我们知道其相当于浏览器当前窗口的 CSS 像素值,也是 Meta Viewport 标签的 width 属性值。

rem 单位在 CSS 规范中明确规定是相对于 html 节点的 font-size 来做计算的,如 2rem 就是 htmlfont-size 大小的两倍

上面的代码中,将 html 节点的 font-size 设置为页面 clientWidth(视口宽度)的 1/10,即 1rem 就等于页面视口的 1/10,这就意味着我们后面使用的 rem 都是按照页面比例来计算的。

这样面对不同的设备独立像素机型,如 375 和 414 的设备独立像素差异,可以根据跟元素的比例来换算下,即在 1 倍的设计稿中,在设备独立像素的层面将差异解决掉了,然后【设备独立像素】通过【设备像素比】转为【设备物理像素】进行渲染。差不多是完美移动端适配了(注意 1 像素、位图放大模糊的两个问题)

实现层面上,我们只需要将 UI 出的图转换为 rem 即可。

iPhone6 为例:视口为 375px,则 1rem = 37.5px,这时 UI 给定一个元素的宽为 75px(设备独立像素,即 1 倍设计稿),我们只需要将它设置为 75 / 37.5 = 2rem

设计师应该使用 1 倍图(设计独立/逻辑像素)进行设计

当然,每个布局都要计算非常繁琐,我们可以借助 PostCSSpxtorem 插件来帮助我们完成这个过程(webpack 编译时此插件完成换算),所以直接写 px 单位即可。

通过监听 windowresize 事件(窗口大小改变)和 pageShow 事件(页面加载),可以保证在遇到不同机型时,可以自动调用上面代码中的 setRemUnit 函数,来自动调整 htmlfontSize 大小。

window.addEventListener('resize', setRemUnit)
window.addEventListener('pageshow', function (e) {
    if (e.persisted) {
      setRemUnit()
    }
})

其实这种计算视口的方案 CSS 规范中早就提供了 vw/vh 单位,但以前浏览器兼容性不佳,所以才有了阿里的 flexible 方案,使用 rem 单位来模拟 vw/vh 单位的效果。

后来由于 vw/vh 单位兼容性越来越好,flexible 方案没必要使用了

flexible 的小问题

注意,flexible 使用方案是用的是 clientWidth 值,也就是 width 指定值。上文有了结论,当 width 指定值 clientWidth 大于 initial-scale 缩放后的值 innerWidth 时,浏览器的当前窗口宽度将丢弃 width 指定值,使用 initial-scale 缩放后的值(不管大小,必须是 initial-scale 优先级高,不然缩放就没效果了)。

所以网页文档的 Meta Viewport 标签的 width=device-width 的情况下,不要让 initial-scale 缩放超过 100%,只要超过等于是放大页面,clientWidth 就大于 innerWidth,不再代表当前视口,flexible 方案的 rem 布局就不准确了。

不过实际开发中,没人会这么设置 Meta Viewport 标签。

vw/vh 布局

上文说过 flexible 方案是用来模拟 vw/vh 单位的,从上图的兼容性看,目前很完美,放心用。

vw、vh 单位将视口宽度、视口高度等分为 100 份。

注意 vw 所参考的并不是 document.documentElement.clientWidth,而是 window.innerWidth 值,即 initial-scale 缩放后的浏览器窗口宽度 CSS 像素值。

官方直接使用了正确的打开方式 😄,不会有 flexible 的小问题

  • vw (Viewport's width)1vw 等于视口宽度的 1%
  • vh (Viewport's height)1vh 等于视口高度的 1%
  • vmin : vwvh 中的较小值
  • vmax : 选取 vwvh 中的最大值

如果视口(设备独立像素,因为移动端浏览器都是强制全屏运行的,所以等于手机屏幕宽度)为 375px,那么 1vw = 3.75px,这时 UI 给定一个元素的宽为 75px(设备独立像素,1 倍设计稿),我们只需要将它设置为 75 / 3.75 = 20vw

这里的比例关系我们也不用自己换算,我们可以使用 PostCSSpostcss-px-to-viewport 插件帮我们完成这个过程。

写代码时,我们只需要根据 UI 给的设计图写 px 单位即可(前提是设计稿为 1 倍图,采用设备独立像素设计的)。

vw 同样有一个小缺陷:px 转换成 vw 不一定能完全整除,因此有一定的像素差,但是这个差异很小可以忽略不计。

安全区域

iPhoneX 发布后,许多厂商相继推出了具有边缘屏幕的手机(也称异形屏)。

这些手机和普通手机在外观上无外乎做了三个改动:圆角corners)、刘海sensor housing)和小黑条Home Indicator)。为了适配这些手机,安全区域这个概念变诞生了:安全区域就是一个不受上面三个效果的可视窗口范围

为了保证页面的显示效果,我们必须把页面限制在安全范围内,但是不影响整体效果。

viewport-fit

viewport-fit 是专门为了适配 iPhoneX 而诞生的一个属性,它用于限制网页如何在安全区域内进行展示。

contain:可视窗口完全包含网页内容

cover:网页内容完全覆盖可视窗口

默认情况下或者设置为 autocontain 效果相同。

env、constant

我们需要将顶部和底部合理的摆放在安全区域内,iOS11 新增了两个 CSS 函数 envconstant,用于设定安全区域与边界的距离。 函数内部可以是四个常量:

  • safe-area-inset-left:安全区域距离左边边界距离
  • safe-area-inset-right:安全区域距离右边边界距离
  • safe-area-inset-top:安全区域距离顶部边界距离
  • safe-area-inset-bottom:安全区域距离底部边界距离

注意:我们必须指定 viweport-fit 后才能使用这两个函数:

<meta name="viewport" content="viewport-fit=cover">

constantiOS < 11.2 的版本中生效,enviOS >= 11.2 的版本中生效,这意味着我们往往要同时设置他们,将页面限制在安全区域内:

body {
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

横屏适配

很多视口我们要对横屏和竖屏显示不同的布局,所以我们需要检测在不同的场景下给定不同的样式:

JavaScript 检测横屏

window.orientation: 获取屏幕旋转方向

window.addEventListener("resize", ()=>{
    if (window.orientation === 180 || window.orientation === 0) { 
      // 正常方向或屏幕旋转180度
        console.log('竖屏');
    };
    if (window.orientation === 90 || window.orientation === -90 ){ 
       // 屏幕顺时钟旋转90度或屏幕逆时针旋转90度
        console.log('横屏');
    }  
}); 

CSS 检测横屏

@media screen and (orientation: portrait) {
  /*竖屏...*/
} 
@media screen and (orientation: landscape) {
  /*横屏专用的样式...*/
}

1px 问题

移动端 1px 解决方案 - 掘金

为了适配各种屏幕,我们写代码时一般使用【设备独立像素】来对页面进行布局。

而在设备像素比大于 1 的屏幕上,我们写的 1px 实际上是被多个物理像素渲染,这就会出现 1px 在有些屏幕上看起来很粗的现象。

根本原因: 750px 的设计稿上是 UI 设计师期待的 1px 物理像素,它对应实际 375px 稿子上的 0.5px 设备独立像素; 而 0.5px 设备独立像素对于 IOS8+ 支持,对于安卓系统则不支持; 所以安卓会将 0.5px 的设备独立像素渲染成 1px 的设备独立像素,也就是说,安卓在 375px 稿子上的设备独立像素为 1px 时,占 2px 物理像素,更粗。

假的 1 像素:仔细对比边框粗细

真的 1 像素:仔细对比边框粗细

媒体查询 @media

媒体查询利用设备像素比缩放,设置 小数 像素

缺点:兼容性差,目前之有 IOS8+ 才支持,在 IOS7 及其以下、安卓系统都是显示 0px

IOS8+ 下已经支持带小数的 px 值,media query 对应 devicePixelRatio 有个查询值 -webkit-min-device-pixel-ratio

.border { border: 1px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2) {
    .border { border: 0.5px solid #999 }
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {
    .border { border: 0.333333px solid #999 }
}

边框图片 border-image

基于 media 查询判断不同的设备像素比给定不同的 border-image

缺点:需要制作一个 1 像素的图片

为了方便复用,多处使用,不用挨个修改,不建议将图片转换成 base64 形式

.border_1px{
    border-bottom: 1px solid #000;
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
    .border_1px{
        border-bottom: none;
        border-width: 0 0 1px 0;
        border-image: url(../img/1pxline.png) 0 0 2 0 stretch;
    }
}

背景图片 background-image

border-image 类似,准备一张符合条件的边框背景图,模拟在背景上。

缺点:需要制作一个 1 像素的图片

为了方便复用,多处使用,不用挨个修改,不建议将图片转换成 base64 形式

.border_1px{
    @media only screen and (-webkit-min-device-pixel-ratio:2){
        .border_1px{
            background: url(../img/1pxline.png) repeat-x left bottom;
            background-size: 100% 1px;
        }
    }
}

背景图片渐变 linear-gradient

利用 background-image 的 linear-gradient 属性(背景图片渐变),从有色到透明,默认方向从上到下,从 0deg50% 的地方颜色是边框颜色,然后下边一半颜色就是透明了。

然后设置背景宽度 100%(根据需要设置宽度),高度是 1px,再去掉平铺行为 no-repeat,所以有颜色的就是 0.5px 的边框,推荐使用

.bg_border {
    background-image: linear-gradient(0deg,black 50%,transparent 50%);
    background-size: 100% 1px;
    background-repeat: no-repeat;
}

阴影模拟 box-shadow

利用阴影也可以实现,优点是没有圆角问题,缺点是颜色不好控制

div {
    box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.5);
}

box-shadow 属性的用法:

`box-shadow: h-shadow v-shadow [blur] [spread] [color] [inset]`
  • 参数分别表示: 水平阴影位置垂直阴影位置模糊距离阴影尺寸阴影颜色外阴影改为内阴影,后四个可选;
  • 重点注意:为何将阴影尺寸设置为负数?设置成 -1px ?首先需要明白【阴影尺寸】的意思:当阴影尺寸为正整数时,整个阴影都会扩大范围,为负数时,整个阴影缩小,当正好为负 1 时,可以实现设备独立像素下的 0.5px 分割线效果

动态缩放 initial-scale

通过 JS 获取设备像素比,再动态的设置 initial-scale 缩放,让 CSS 虚拟像素等于真正的物理像素,注意不是等于设备独立像素,只有缩放固定 100% 才是 CSS 像素等于设备独立像素。而动态设置缩放的行为,是打破系统的倍率行为,手动控制。

例如:当 iphoneXR 设备像素比为 3 ,我们将页面缩放 1/3 倍,这时 1px 等于 1 个真正的屏幕的设备物理像素,等于 0.33333333 个设备独立像素,这下线条不再粗了。

const scale = 1 / window.devicePixelRatio;
const viewport = document.querySelector('meta[name="viewport"]');
if (!viewport) {
    viewport = document.createElement('meta');
    viewport.setAttribute('name', 'viewport');
    window.document.head.appendChild(viewport);
}
viewport.setAttribute('content', 'width=device-width,user-scalable=no,initial-scale=' + scale + ',maximum-scale=' + scale + ',minimum-scale=' + scale);

但是! 我们知道 Meta Viewport 是个全局影响的元信息标签,这么设置等于是自废武功,接下来书写的所有 CSS 像素单位都不是自适配了,因为用设备物理像素来表示,不同的设备像素比机型上,如 375px 设计稿上的一个 20px 的图标,在设备像素比越大的机型,20px 占用的设备独立像素就越小,如 iphoneX 上,20/2 = 10 个独立像素,iphoneXR 上,20/3 = 6.6666 个独立像素。

上面这种方案是早先 flexible 采用的方案,没看它的源码,暂时不清楚是如何解决这个问题的,咱们肯定是不可用的

伪类 + transform

基于 media 查询判断不同的设备像素比对线条进行 transformscale 缩放,这种方式可以满足各种场景,没有兼容性问题,最推荐使用

如果需要满足圆角,只需要给伪元素也加上 border-radius 即可

.border_1px:before{
    content: '';
    position: absolute;
    top: 0;
    height: 1px;
    width: 100%;
    background-color: #000;
    transform-origin: 50% 0%;
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
    .border_1px:before{
        transform: scaleY(0.5);
    }
}
@media only screen and (-webkit-min-device-pixel-ratio:3){
    .border_1px:before{
        transform: scaleY(0.33);
    }
}

还有一点需要注意:空元素不可以使用伪元素,什么是空元素?不能包含子节点的元素(确实符合语义,伪元素也是子节点),我们简单理解为单标签即可。

常见的空元素有 input、select、textarea 等表单元素。而最常踩的坑是在 textarea 上设置 1px 的边框,既然不支持,使用其他方案就好了。

具体有哪些空元素?看 MDN-空元素

矢量 SVG

上面我们 border-imagebackground-image 都可以模拟 1px 边框,但是使用的都是位图,还需要外部引入。

借助 PostCSSpostcss-write-svg 插件,我们能直接使用 border-imagebackground-image 创建 svg1px 边框:

@svg border_1px { 
    height: 2px; 
    @rect { 
        fill: var(--color, black); 
        width: 100%; 
        height: 50%; 
    } 
} 

.example { 
    border: 1px solid transparent; 
    border-image: svg(border_1px param(--color #00b1ff)) 2 2 stretch; 
}

编译后:

.example {
    border: 1px solid transparent;
    border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E")
        2 2 stretch;
}

图片模糊问题

我们平时使用的图片大多数都属于位图(png、jpg...),位图由一个个像素点构成的,每个像素都具有特定的位置和颜色值:

理论上,位图的每个像素对应在屏幕上使用一个物理像素来渲染,才能达到最佳的显示效果。

而在 dpr > 1 的屏幕上,位图的一个像素可能由多个物理像素来渲染,然而这些物理像素点并不能被准确的分配上对应位图像素的颜色,只能取近似值,所以相同的图片在 dpr > 1 的屏幕上就会模糊:

为了保证图片质量,我们应该尽可能让一个屏幕像素来渲染一个图片像素,所以,针对不同 DPR 的屏幕,我们需要展示不同分辨率的图片。

如:在 dpr=2 的屏幕上展示两倍图 (@2x),在 dpr=3 的屏幕上展示三倍图 (@3x)

媒体查询 @media

使用 @media 查询判断不同的设备像素比来显示不同精度的图片:

// 只适用于背景图
.avatar{
    background-image: url(conardLi_1x.png);
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
    .avatar{
        background-image: url(conardLi_2x.png);
    }
}
@media only screen and (-webkit-min-device-pixel-ratio:3){
    .avatar{
        background-image: url(conardLi_3x.png);
    }
}

背景图片 image-set

在 CSS 中,使用 background-image 属性的 srcset 值,浏览器会自动根据像素密度匹配最佳显示图片,即浏览器自动挑图片,咱不用操心,提供更高分辨率的图片就行

// 只适用于背景图
.avatar {
    background-image: -webkit-image-set( "conardLi_1x.png" 1x, "conardLi_2x.png" 2x );
}

别忘了带浏览器前缀,caniuse 查看兼容性

图片标签 srcset

在模版/HTML/JSX 中,使用 img 标签的 srcset 属性,浏览器会自动根据像素密度匹配最佳显示图片,即浏览器自动挑图片,咱不用操心,提供更高分辨率的图片就行:

<img src="conardLi_1x.png"
     srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x">

矢量图 SVG

SVG 全称是可缩放矢量图Scalable Vector Graphics)。

不同于位图的基于像素,SVG 则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真

除了我们手动在代码中绘制 svg,我们还可以像使用位图一样使用 svg 图片,加起来一共 4 种。

// 1. 使用 svg 标签手动绘制
<svg>...</svg>

// 2. img 标签直接引入 svg 图片
<img src="conardLi.svg">

// 3. 将较小的 svg 图片转为 base64 编码,直接内嵌在 img 标签中,不依靠外部资源
<img src="data:image/svg+xml;base64,[data]">

// 4. 在 css 中通过背景图片引入 svg 图片
.avatar {
  background: url(conardLi.svg);
}

替换图片 src 地址

在 js 中,使用 window.devicePixelRatio 获取设备像素比,遍历所有图片,替换图片地址:

const dpr = window.devicePixelRatio;
const images =  document.querySelectorAll('img');
images.forEach((img)=>{
  img.src.replace(".", `@${dpr}x.`);
})

自适应与响应式

移动端与自适应、响应式布局方案分割不开,关于响应式布局和自适应布局的概念、区别、优缺点和如何选择?

概念区别

自适应布局: 响应式布局就是实现不同屏幕分辨率的终端上浏览网页的不同展示方式。通过响应式设计能使网站在手机和平板电脑上有更好的浏览阅读体验。换句话说就是一个网站能够兼容多个终端,而不是为了每一个终端做一个特定的版本。

  1. 通过检测视口分辨率,来判断当前访问的设备是:pc 端、平板、手机,从而请求服务层,返回不同的页面;
  2. 需要开发多套界面,对页面做的屏幕适配是在一定范围:比如 pc 端一般要大于 1024 像素,手机端要小于 768 像素。
  3. 如果屏幕太小会发生内容过于拥挤。

响应式布局: 自适应布局就是指能忘了使网页自适应的显示在不同大小终端设备上的新网页设计方式及技术,它需要开发多套界面来适应不同的终端。

  1. 通过检测视口分辨率,针对不同客户端在客户端做代码处理,来展现不同的布局和内容。

  2. 只需要开发一套界面就可以了,是一套页面全部适应。

  3. 可以自动识别屏幕宽度并做出相应调整的网页设计。

优缺点

自适应布局:

  1. 优点:可以独立设计,做任何自己想要的风格,还可以做到数据库同步,设计方案灵活,可独立优化符合搜索引擎的规则。
  2. 缺点:手机站有时无法做到与 PC 站内容完全一致,且工作量大,自适应手机站往往使用不同的子域名或目录跳转,对于优化来讲权重分散,访客浏览体验仍有缺陷。

响应式布局:

  1. 优点:更加方便浏览,能够增加访客的体验度,无需再单独设计制作手机站,PC 站即是手机站,对于优化来讲,权重不分散,更加符合搜索引擎的规则。
  2. 缺点:设计往往风格有些局限,对于复杂的框架结构难以实现,并且前端 css 代码量增大。

使用场景

选择自适应网站:主要因为自己在建设手机站之前已经有了 PC 站,PC 站不能做到小屏幕设备适中浏览,又不能进行大改版影响网站优化,因此建设自适应的手机站跳转来实现手机、ipad 等符合浏览,手机站数据库一般为同步。

选择响应式网站:往往是建设新站时设计,之前无 PC 站或手机,不需要顾及网站优化及数据同步,可设计为响应式网站,响应式网站更加符合访客浏览,增加网站体验度,也更符合网站优化工作。

总之:响应式布局还是要比自适应布局要好一点,但是自适应布局更加贴切实际,因为你只需要考虑几种状态就可以了而不是像响应式布局需要考虑非常多状态。所以的说无论哪种设计都有它们各自的特点,我们要根据项目的需求来选择适合的布局方式。

响应式实现

自适应方案需要开发多套设备,麻烦。所以更流行响应式布局,只要开发一套就够,缺点就是 CSS 比较重,下面介绍具体技术实现:

原文链接

媒体查询

CSS3 媒体查询可以让我们针对不同的媒体类型定义不同的样式,当重置浏览器窗口大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。

如何确定媒体查询的分割点也是一个开发中会遇到的问题,下面是市场上的移动设备和电脑屏幕分辨率的分布情况,可以发现不同品牌和型号的设备屏幕分辨率一般都不一样。如果我们选择 600px900px1200px1800px 作为分割点,可以适配到常见的 14 个机型:

当然这只是其中的一种分割方案,我们还可以这样划分:480px800px1400px1400px

而作为曾经典型的响应式布局框架,Bootstrap 是怎么进行断点的呢?

上面的分割方案不一定满足项目中的实际需求,我们可以先用跨度大的分割点进行分割,如果出现不适配的情况可以再根据实际情况增加新的分割点。

移动优先 OR PC 优先

不管是移动优先还是 PC 优先,都是依据当随着屏幕宽度增大或减小的时候,后面的样式会覆盖前面的样式。因此,移动端优先首先使用的是 min-width,PC 端优先使用的 max-width

百分比布局

通过百分比单位,可以使得浏览器中组件的宽和高随着浏览器的高度的变化而变化,从而实现响应式的效果。Bootstrap 里面的栅格系统就是利用百分比来定义元素的宽高,CSS3 支持最大最小高,可以将百分比和 max(min) 一起结合使用来定义元素在不同设备下的宽高。

/* iphone6 7 8 plus */
@media screen and (max-width: 414px) {
    aside {
      float: none;
      width: 100%;
      height: 5%;
      background-color: yellow;
    }
    main {
      height: calc(100vh - 5%);
      background-color: red;
    }
}
/* iphoneX */
@media screen and (max-width: 375px) and (-webkit-device-pixel-ratio: 3) {
    aside {
      float: none;
      width: 100%;
      height: 10%;
      background-color: blue;
    }
    main {
      height: calc(100vh - 10%);
      background-color: red;
    }
}
/* iphone6 7 8 */
@media screen and (max-width: 375px) and (-webkit-device-pixel-ratio: 2) {
    aside {
      float: none;
      width: 100%;
      height: 3%;
      background-color: black;
    }
    main {
      height: calc(100vh - 3%);
      background-color: red;
    }
}
/* iphone5 */
@media screen and (max-width: 320px) {
    aside {
      float: none;
      width: 100%;
      height: 7%;
      background-color: green;
    }
    main {
      height: calc(100vh - 7%);
      background-color: red;
    }
}

但是我们必须要弄清楚 css 中子元素的百分比到底是相对谁的百分比。直接上结论吧:相对于元素的包含块(MDN)来计算的。

子元素的 heightwidth 中使用百分比,是相对于子元素的直接父元素,width 相对于父元素的 widthheight 相对于父元素的 height

子元素的 topbottom 如果设置百分比,则相对于直接非 static 定位(默认定位)的父元素的高度,同样子元素的 leftright 如果设置百分比,则相对于直接非 static 定位(默认定位的)父元素的宽度。

当一个元素没有书写 position 属性时,默认等同于 position: static 值

子元素的 padding 如果设置百分比,不论是垂直方向或者是水平方向,都相对于直接父亲元素的 width,而与父元素的 height 无关。跟 padding 一样,margin 也是如此,子元素的 margin 如果设置成百分比,不论是垂直方向还是水平方向,都相对于直接父元素的 width

border-radius 不一样,如果设置 border-radius 为百分比,则是相对于自身的宽度,除了 border-radius 外,还有比如 translatebackground-size 等都是相对于自身的。

html 根元素时直接继承浏览器的当前窗口宽度,写不写百分比一个意思。

从上述对于百分比单位的介绍我们很容易看出如果全部使用百分比单位来实现响应式的布局,有明显的以下两个缺点:

  • 计算困难,如果我们要定义一个元素的宽度和高度,按照设计稿,必须换算成百分比单位。
  • 可以看出,各个属性中如果使用百分比,相对父元素的属性并不是唯一的。比如 widthheight 相对于父元素的 widthheight,而 marginpadding 不管垂直还是水平方向都相对比父元素的宽度、border-radius 则是相对于元素自身等等,造成我们使用百分比单位容易使布局问题变得复杂。、
  • 正确方式,使用上文的《移动端适配方案》