移动端适配原理与方案详解

13,093 阅读15分钟


移动端适配是指在不同尺寸的手机设备上,页面能相对达到合理的展示(响应式)或者保持统一效果的等比缩放(看起来差不多)。本文介绍了移动端适配中需要掌握的基本概念和移动端页面开发中常用的适配方案。

一、基本概念

在做移动端适配之前,我们必须了解一下不同设备之间,显示器或者屏幕究竟有哪些不同。与此相关的概念比较多,也特别容易混淆,这里将对涉及到的主要概念一一介绍。这其中包括硬件中的概念、操作系统或软件中的概念和浏览器中的概念。

如果你觉得混乱了,不要着急,就以 iphone6 为例,细细品味。

1、硬件概念

屏幕尺寸

以 iPhone6 为例,其屏幕尺寸为 4.7英寸,指的是屏幕对角线的长度为 4.7 英寸,1in = 2.54cm。

屏幕分辨率(物理分辨率、设备分辨率)

以 iPhone6 为例,其屏幕分辨率为 750 x 1334,指的是屏幕水平有 750 个像素点,垂直有 1334 个像素点。

设备像素(物理像素)

设备像素(Device Pixels)是显示设备中一个最微小的物理部件,和上面所讲的设备分辨率相对应。

像素密度(Pixels Per Inch)

以 iPhone6 为例,其屏幕像素密度为 326ppi,指的是每英寸有 326 个像素点。

计算公式如下:
image.png
注意,像素密度指的并不是每平方英寸中包含的像素点数量,而是屏幕水平或垂直的一条直线上每英寸包含的像素点数量。例如,iPhone6 屏幕宽度为 2.3 英寸,高度为 4.1 英寸,其屏幕像素密度就等于 750 / 2.3 = 326 或 1334 / 4.1 = 326。

2、系统概念

显示分辨率

通常,PC 的显示器分辨率是可以设置的,显示分辨率就是指系统或用户设置的分辨率。用户可以通过修改显示分辨率,修改系统字体大小。通常,显示分辨率小于等于设备分辨率。

以我的 MacBook Pro (13-inch, 2020) 为例,其设备分辨率为 2560 x 1600,系统默认分辨率为 1440 x 900,用户也可以自己设置为 1680 x 1050、1280 x 800、1024 x 640 等几个分辨率。

系统设置分辨率生效是通过算法进行了转换。当用户设置不同的分辨率时,系统将自动根据显示分辨率和物理分辨率的比值来拟合。例如,如果物理分辨率为2560 x 1600,而用户设置的显示分辨率为 1280 x 800,那么 1 个显示像素点将由 4 个物理像素点显示;如果用户设置的分辨率为 1440 x 900,物理分辨率和显示分辨率的比值并不是整数,那么系统将根据各个像素点的色值和亮度计算拟合显示。

设备独立像素(逻辑像素)

设备独立像素(又称设备无关像素 Device Independent Pixels 、密度独立性像素 Density Independent Pixels,简称 DIP 或 DP),是一种物理测量单位,基于计算机控制的坐标系统和抽象像素(虚拟像素),由底层系统的程序使用,转换为物理像素的应用(from 百度百科)。设备独立像素可以简单认为是计算机坐标系统中的一个点。

典型的用途是允许移动设备软件将信息显示和用户交互扩展到不同的屏幕尺寸。允许应用程序以抽象像素为单位进行测量,而底层图形系统将应用程序的抽象像素测量值转换为适合于特定设备的物理像素。

逻辑分辨率

逻辑分辨率用屏幕的宽*高来表示(单位:设备独立像素)。

通过 screen.width/height 得到的数值就是整个屏幕(不仅仅是浏览器的区域)的宽度和高度(单位:设备独立像素)。在 PC 中,这个数值就是系统设定的显示分辨率的大小。这个数值不随页面缩放、浏览器窗口大小而改变。对于移动端,这个数值可以在 Chrome 开发者工具中,打开移动端调试,页面顶部显示的就是逻辑分辨率。

以 iPhone6 为例,其逻辑分辨率(设备独立像素数)为 375 × 667。

image.png

3、浏览器中的概念

设备像素比(DPR)

设备独立像素可以简单认为是计算机坐标系统中的一个点,而物理像素可以简单认为是显示器硬件中的一个点。那么它们之间的关系是怎样的呢?设备像素比就是表示二者之间关系的一个概念。

设备像素比就是指设备物理像素和设备独立像素之间的比值。在 JS 中可以通过 window.devicePixelRatio 获取。可以简单理解为:硬件中一个点的大小和系统中一个点的大小的比值。对于某一个固定的设备,这个值是固定不变的。

以 iPhone6 为例,其设备像素比为 2,指的是用其物理像素和逻辑像素的比值为 2。

当然,也有一些例外。iPhone6/7/8Plus 的实际物理像素是 1080x1920,在开发者工具中我们可以看到:它的设备独立像素是 414x736,设备像素比为 3,设备独立像素和设备像素比的乘积并不等于 1080x1920,而是等于 1242x2208。实际上,手机会自动把 1242x2208 个像素点塞进 1080x1920 个物理像素点来渲染,我们不用关心这个过程,而 1242x2208 被称为屏幕的设计像素。
image.png

视口(Viewport)

视口的概念通常用于移动端。一般我们所说的视口共包括三种:布局视口、视觉视口和理想视口,它们在屏幕适配中起着非常重要的作用。

  1. 布局视口(Layout Viewport)

image.png

布局视口是网页布局的基准窗口。在 PC 上,布局视口就等于当前浏览器的窗口大小;在移动端,布局视口被赋予一个默认值,这个值通常比该设备的逻辑分辨率大很多,这保证 PC的网页可以在手机浏览器上呈现,但这样网页元素就会显得非常小,用户可以手动对网页进行放大查看。

以 iPhone6 为例,其布局视口的宽度为 980px。

可以通过调用 document.documentElement.clientWidth/clientHeight 来获取布局视口大小。在进行 @media 媒体查询的时候,查询的宽度值也是布局视口的宽度值。

  1. 视觉视口(Visual Viewport)

image.png
视觉视口指用户通过屏幕真实看到的区域。视觉视口默认等于当前浏览器的窗口大小(包括滚动条宽度)。可以通过调用 window.innerWidth/innerHeight 来获取视觉视口大小。

当用户对浏览器进行缩放时,不会改变布局视口的大小,所以页面布局是不变的,但是缩放会改变视觉视口的大小。例如:用户将浏览器窗口放大了 200%,这时浏览器窗口中的 CSS像素会随着视觉视口的放大而放大,这时一个 CSS像素会跨越更多的物理像素。所以,布局视口会限制你的 CSS布局而视觉视口决定用户具体能看到什么。

  1. 理想视口(Ideal Viewport)

image.png
布局视口在移动端展示的效果并不是一个理想的效果,所以理想视口就诞生了:网站页面在移动端展示的理想大小。如上图,在浏览器调试移动端时页面上给定的像素大小就是理想视口大小,其实也就是逻辑分辨率的大小。

通过 meta viewport 设置布局视口

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

viewport 有以下字段可配置:

width设置布局视口的宽度,为一个正整数,或字符串"width-device"
initial-scale设置页面的初始缩放值,为一个数字,可以带小数
minimum-scale允许用户的最小缩放值,为一个数字,可以带小数
maximum-scale允许用户的最大缩放值,为一个数字,可以带小数
height设置布局视口的高度,这个属性对我们并不重要,很少使用
user-scalable是否允许用户进行缩放,值为"no"或"yes"

例如:

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

以上语句的含义为:

  • width=device-width 表示将布局视口设置成为设备屏幕的宽度(即理想视口的宽度,或逻辑分辨率,iPhone6 为 375px);
  • initial-scale=1 的意思是初始缩放的比例是 1,使用它的时候,同时也会将布局视口的尺寸设置为缩放后的尺寸;而缩放的尺寸就是基于屏幕的宽度来的,也就起到了和 width=device-width 同样的效果。另外,值得一提的是,我们在进行媒体查询的时候,查询的宽度值其实也是布局视口的宽度值。
  • maximum-scale=1.0, minimum-scale=1.0, user-scalable=no 的目的是阻止用户缩放;(需要注意的是,在 ios10+以上,尽管开发者设置了 user-scalable=no,Safari 还是允许用户通过手势来缩放,安卓手机各大厂商的内置浏览器也逐渐开放用户缩放,即便使用 meta 标签进行设置)

长度单位:rem 和 em

  • rem 即:‘The font size of the root element’,就是以根元素 的字体大小为基本单位,是一种相对单位。rem 适配的原理就是以 html 的 font-size 大小为基本单位来布局。
  • em 是另一种相对单位,它相对于该元素本身的 font-size 值来计算。

二、移动端适配方案

1、媒体查询

通过 CSS 的 @media 媒体查询设置不同的 style。通过媒体查询,我们可以根据不同屏幕设置不同样式,这样就可以实现不同屏幕的适配。

link 元素中的 CSS 媒体查询,不同屏幕加载不同样式文件:

<link rel="stylesheet" media="(max-width: 500px)" href="mobile.css" />
<link rel="stylesheet" media="(min-width: 980px)" href="pc.css" />

CSS 样式表中的媒体查询:

@media only screen and (max-width: 414px){
  html{
    font-size: 64px;
  }
}
@media only screen and (max-width: 375px){
  html{
    font-size: 58px;
  }
}
@media only screen and (max-width: 360px){
  html{
    font-size: 56px;
  }
}
@media only screen and (max-width: 320px){
  html{
    font-size: 50px;
  }
}

2、动态 rem 方案(淘宝 flexible 方案)

上面我们介绍了 CSS 中的一个相对长度单位 rem,其大小由根元素字体大小决定。

我们可以采用 rem 为单位设置元素大小。对于不同屏幕,我们只需要动态修改根元素字体大小,元素大小就会同比例改变,从而做到页面的自动适配效果。例如,假设设计稿宽度为 750px,元素A宽度为 300px,在屏幕宽度为 375pt 的屏幕中,设置根元素字体大小为 37.5px,则元素A宽度为 4rem;在屏幕宽度为 750pt 的屏幕中,只需将根元素字体大小改为 75px,不需要改变元素A的大小,就可以做到页面适配。

手淘团队的 flexible 方案就是根据以上原理实现的,点此查看DEMO。其核心代码如下:

(function flexible (window, document) {
  var docEl = document.documentElement
  
 	// 根据屏幕宽度设置根元素字体大小,set 1rem = viewWidth / 10
  function setRemUnit () {
    var rem = docEl.clientWidth / 10
    docEl.style.fontSize = rem + 'px'
  }

  setRemUnit()

  // reset rem unit on page resize
  window.addEventListener('resize', setRemUnit)
  window.addEventListener('pageshow', function (e) {
    if (e.persisted) {
      setRemUnit()
    }
  })
}(window, document))

根据上述方案,我们需要将设计稿中的 px 转化为 rem,如果每一次都需要自己计算 px 转换 rem,就太麻烦了。为了简化改过程,衍生出很多 px 转换 rem 的小工具。其中使用最为广泛的是 postcss-px2rem 。使用该用具,在实际开发中直接按照设计稿写 px 就可以了。

示例:

.selector {
    width: 150px;
    height: 64px; /*px*/
    font-size: 28px; /*px*/
    border: 1px solid #ddd; /*no*/
}

使用插件转换后:

.selector {
    width: 2rem;
    border: 1px solid #ddd;
}
[data-dpr="1"] .selector {
    height: 32px;
    font-size: 14px;
}
[data-dpr="2"] .selector {
    height: 64px;
    font-size: 28px;
}
[data-dpr="3"] .selector {
    height: 96px;
    font-size: 42px;
}

文本字号不建议使用 rem。前面大家都见证了如何使用 rem 来完成 H5 适配。那么文本又将如何处理适配。是不是也通过rem来做自动适配。显然,我们在 iPhone3G 和 iPhone4 的 Retina 屏下面,希望看到的文本字号是相同的。也就是说,我们不希望文本在 Retina 屏幕下变小,另外,我们希望在大屏手机上看到更多文本,以及,现在绝大多数的字体文件都自带一些点阵尺寸,通常是 16px 和 24px,所以我们不希望出现 13px 和 15px 这样的奇葩尺寸。如此一来,就决定了在制作 H5 的页面中,rem 并不适合用到段落文本上。

总结一下,使用动态 rem 方案需要做的工作:

  1. meta 标签设置 viewport 宽度为屏幕宽度;
  2. 根据不同屏幕修改根元素 font-size 大小,一般设置为屏幕宽度的十分之一(可引入 lib-flexible 库,或者自己写相应逻辑);
  3. 开发环境配置 postcss-px2rem 或者类似插件;
  4. 根据设计稿写样式,元素宽高直接取设计稿宽高即可,单位为 px,插件会将其转换为 rem;
  5. 段落文本也按照设计稿写,单位为px,不需要转换为 rem;

3、Viewport 方案(推荐)

上面介绍的动态 rem 方案,其本质是让页面元素大小跟随屏幕宽度的变化成比例缩放。CSS Viewport units (视口单位)正是一种相对于屏幕宽高的一种长度单位,并且兼容性越来越好。视口单位有:vw、vh、vmin和vmax。vw 单位表示根元素宽度的百分比,1vw 等于视口宽度的1%。

lib-flexible 文档中有如下一段话,表示 flexible 方案已经可以废弃,推荐使用 vw 视口单位进行适配。

由于 viewport 单位得到众多浏览器的兼容,lib-flexible 这个过渡方案已经可以放弃使用,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用 viewport 来替代此方案。

vw 适配方案的流程:

  1. meta 标签设置 viewport 宽度为屏幕宽度;
  2. 开发环境配置 postcss-px-to-viewport 或者类似插件;
  3. 根据设计稿写样式,元素宽高直接取设计稿宽高即可,单位为 px,插件会将其转换为 vw;
  4. 段落文本也按照设计稿写,单位为px,不需要转换为 vw;

4、固定 Viewport 宽度方案(整体缩放)

所谓固定 viewport 宽度,即将网页布局视口的宽度设置为设计稿的宽度,不管是在哪种设备上,网页的布局视口宽度都是固定的。开发过程中,直接采用设计稿中的宽高设置元素大小、字体大小,单位采用 px 即可。

例如,如果设计稿的宽度为 750px,那么我们就可以将布局视口设置如下:

<meta name="viewport" content="width=750, user-scalable=no" />

这种方案,其实相当于将页面整体缩放。其本质上是对页面进行线性缩放来适应不同大小的屏幕。这种方案对于一定尺寸范围内的设备是可以的。这种方案简单方便,但如果想开发出跨越平板和手机的界面则不太合适。因为手机和平板屏幕尺寸差异太大,会导致缩放太大、界面元素显得不太和谐。这种方案适用于一些简单的活动页,或者对页面适配要求不高的页面。

可以点击这个链接查看效果。可以发现,这种方案将页面所有元素,进行了等比缩放。在手机上效果可能差别不大,但是在 ipad 上,就会显得很不协调,文字显得偏大很多。

image.png

image.png

三、问题

1. 移动端中,什么是一像素边框问题?为什么会存在?怎么解决?

答:移动端中,从苹果推出视网膜屏之后,移动端中设备像素和逻辑像素便出现了偏差。以 iphone6 为例,其物理像素宽度为 750,而逻辑像素宽度为 375,即一个逻辑像素对应两个物理像素。而在移动端网页开发中,我们通常会设置布局视口宽度为设备逻辑像素宽度,即 375。而一般设计稿是按照 750 设计的,设计稿中的一像素对应于CSS 中便是 0.5 像素,直接设置为 1px 就会显得有点粗了。如果采用上述方案四(固定 Viewport 宽度)进行页面适配,就不会存在一像素边框问题。

有如下几种解决方案:

  • transform scale
  • 背景图片
  • box-shadow

参考