移动端布局

# 设备和视口

# 尺寸

设备屏幕尺寸是指屏幕的对角线长度。比如:iPhone6/7 是 4.7 寸,iPhone6/7p 是 5.5 寸。

1in = 2.54cm
3.5in = 3.5*2.54cm = 8.89cm
4.0in = 4.0*2.54cm = 10.16cm
4.7in = 4.7*2.54cm = 11.938cm
5.0in = 5.0*2.54cm = 12.7cm
5.5in = 5.5*2.54cm = 13.97cm
6.0in = 6.0*2.54cm = 15.24cm

iPhone 尺寸示意图

# 像素

像素是计算机屏幕能显示一种特定颜色的最小区域。在移动 Web 应用中,像素分为:

  • 设备像素(也称物理像素)
  • 逻辑像素(也称设备独立像素和 CSS 像素)

# 设备像素

设备像素(又称物理像素)是物理概念,指的是设备中使用的物理像素,也就是屏幕中的发光点的个数(屏幕由很多个发光点组成,每个发光点可以显示不同的颜色,这些发光的点组成了屏幕)。

比如 iPhone6 的分辨率 750 x 1334。横向有 750 个发光的点,纵向有 1334 个发光的点。所以我们说 iPhone6 的设备水平像素是 750 像素,指的是 750 个发光点。

机型 尺寸(inch) 逻辑分辨率(pt) 设备分辨率(px) 缩放因子(Scale Factor)
3GS 3.5 320 x 480 320 x 480 @1x
4(s) 3.5 320 x 480 640 x 960 @2x
5(s/se) 4 320 x 568 640 x 1136 @2x
6(s)/7/8 4.7 375 x 667 750 x 1334 @2x
6(s)/7/8 Plus 5.5 414 x 736 1242 x 2208 @3x
X 5.8 375 x 812 1125 x 2436 @3x
XR 6.1 414 x 896 828 x 1792 @2x
XS 5.8 375 x 812 1125 x 2436 @3x
XS Max 6.5 414 x 896 1242 x 2688 @3x
7.9" iPad mini 4 6.5 768 x 1024 1536 x 2048 @2x
9.7" iPad 6.5 768 x 1024 1536 x 2048 @2x
10.5" iPad Pro 6.5 834 x 1112 1668 x 2224 @2x
12.9" iPad Pro 6.5 1024 x 1366 2048 x 1732 @2x

# 逻辑像素

逻辑像素又称设备独立像素(DIP,Device Independent Pixel,Density Independent Pixel),简单地来说设备独立像素就是:独立于设备的用于逻辑上衡量像素的单位,是 Web 编程的概念。CSS 样式代码中使用的正是逻辑像素。1 个逻辑像素可能对应多个物理像素(设备发光点)。

设备独立像素 = 逻辑像素 = CSS 像素

在 CSS 规范中,长度单位可以分为两类:

  • 绝对(absolute)单位
  • 相对(relative)单位:常用的 px 像素是一个相对单位,相对的是设备像素(Device Pixel)

在 Apple 的视网膜屏(Retina)中,每 4 个(设备)像素为一组,渲染出普通屏幕中一个像素显示区域内的图像,从而实现更为精细的显示效果。此时,250px 的(逻辑像素的)元素跨越了 500 个物理像素的宽度。

逻辑像素与物理像素对比图

浅显的理解就是可以看作是 2cm x 2cm 的正方形被切割成四块,然后遇到 DPR 为 2 的时候,被切割的四块又被分别切割成四块,但是总面积不变。

如果用户进行了放大,那么一个 CSS 像素还将跨越更多的物理像素。

# 分辨率

屏幕分辨率(Resolution)是指:纵横向上的像素点数(单位是 px),也就是显示器所能显示的像素有多少。由于屏幕上的点、线和面都是由像素组成的,显示器可显示的像素越多,画面就越精细,同样的屏幕区域内能显示的信息也越多。

就相同大小的屏幕而言,当屏幕分辨率低时(例如 640 x 480),在屏幕上显示的像素少,单个像素尺寸比较大。屏幕分辨率高时(例如 1600 x 1200),在屏幕上显示的像素多,单个像素尺寸比较小。

  • 在 PC 端的分辨率常见是:1366*7681440*9001024*7681400*900
  • 移动端常见分辨率:2160*10801920*10801334*7501136*640

在说分辨率的时候我们常常会把大的值说在前面,所以在 PC 端屏幕宽度比高度的值要大一点,第一个值一般是指的宽度,第二个值为高度。移动端正好相反,手机一般宽度都是小于高度,所以第一个值是高度。

# 设备像素密度

PPI(Pixels Per Inch),表示每英寸所拥有的逻辑像素数目,即像素密度(Screen Density)。是图像分辨率的单位,图像 PPI 值越高,画面的细节就越丰富,因为单位面积的像素数量更多。

上述我们知道了屏幕尺寸是指对角线长度,如果又知道了屏幕的分辨率(即知道了宽高的像素值),那么宽高和对角线就形成了一个垂直三角形。利用勾股定理,可以算出对角线的像素值了。而又知道了对角线的英寸值,那么就可以算出屏幕的 PPI 值了:

PPI 计算示意图

屏幕对角线的分辨率也就是屏幕对角线上的像素点数,可以根据已知的横纵分辨率通过勾股定理计算得。

# 设备像素比

DPR(Device Pixel Ratio),设备像素比是默认缩放为 100% 的情况下,设备像素和逻辑像素的比值。

设备像素比 DPR = 设备像素数 / 逻辑像素数

单位之间的换算关系:

  • 1 倍:1pt = 1dp = 1px(iPhone 3GS)
  • 2 倍:1pt = 1dp = 2px(iPhone 6/7/8)
  • 3 倍:1pt = 1dp = 3px(iPhone 6/7/8 Plus)

设备像素比可以通过 window.devicePixelRatio 来获取,或者使用 CSS 中的 device-pixel-ratio。设备像素比不一定都是整数,尤其是 Android 设备十分的碎片化。下面常见的设备像素比:

  • 普通密度桌面显示屏:devicePixelRatio = 1
  • 高密度桌面显示屏(Retina):devicPixelRatio = 2
  • 主流手机显示屏:devicePixelRatio = 2 or 3

# 图像分辨率

图像分辨率表示单位英寸中所包含的像素点数,其定义更趋近于分辨率本身的定义。

从这个定义上来看很明显,跟 PPI 的含义是一样,所以 PPI 是用来表示图像分辨率的单位,如一图片分辨率为 100ppi,含义是每英寸中所含有 100 个像素。对于一张 100px * 100px 的图片,通过 CSS 设置其宽高:

img {
    width: 100px;
    height: 100px;
}

在普通显示屏的电脑中打开是正常的,但假设在手机或 Retina 屏中打开,按照逻辑分辨率来渲染,他们的 deviceRatio = 2,那么就相当于拿 4 个物理像素来描绘 1 个逻辑像素。这等于拿一个 2 倍的放大镜去看图片,图片就会变得模糊。这时,就需要使用 @2x 甚至 @3x 图来避免图片的失真。

# 视口

在 PC 端,视口指的是浏览器的可视区域,其宽度和浏览器窗口的度保持一致。在 CSS 标准文档中,视口也被称为初始包含块,它是所有 CSS 百分比宽度推算的根源,给 CSS 布局限制了一个最大宽度。

移动端浏览器通常宽度是 240px~640px,而大多数为 PC 端设计的网站宽度至少为 800px,如果仍以浏览器窗口作为视口的话,网站内容在手机上看起来会非常窄。因此,移动端引入三个概念,使得移动端中的视口与浏览器宽度不再相关联。

  • 布局视口 Layout Viewport
  • 视觉视口 Visual Viewport
  • 理想视口 Ideal Viewpor

# 布局视口

一般移动设备的浏览器都默认设置了一个 <viewport> 元标签,定义一个虚拟的布局视口(Layout Viewport),用于解决早期的页面在手机上显示的问题。iOS 和 Android 基本都将这个视口分辨率设置为 980px,所以 PC 上的网页基本能在手机上呈现,只不过元素看上去很小,一般默认可以手动缩放网页。

布局视口

布局视口的宽度 / 高度可以通过以下获取:

const layoutViewportWidth = document.documentElement.clientWidth;
const layoutViewportHeight = document.documentElement.clientHeight;

可以看到,默认的布局视口宽度为 980px。如果要显式设置布局视口,可以使用 HTML 中的 <meta> 标签:

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

布局视口使视口与移动端浏览器屏幕宽度完全独立开。CSS 布局将会根据它来进行计算,并被它约束。

我们可以使用视口标签(Viewport <meta> 标签)来显式地设置布局视口:

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

下面是每个属性的详细说明:

属性名 取值 描述
width 正整数或 device-width 定义视口的宽度,单位为像素
height 正整数或 device-height 定义视口的高度,单位为像素,一般不用
initial-scale [0.0-10.0] 定义初始缩放值
minimum-scale [0.0-10.0] 定义放大最大比例,它必须小于或等于 maximum-scale 设置
maximum-scale [0.0-10.0] 定义缩小最小比例,它必须大于或等于 minimum-scale 设置
user-scalable yes / no 定义是否允许用户缩放页面,默认 yes

有几点值得注意:

  • <viewport> 标签只对移动端浏览器有效,对 PC 端浏览器无效
  • 当缩放比例为 100% 时,DIP 的宽度 = CSS 像素宽度 = 理想视口的宽度 = 布局视口的宽度
  • 单独设置 initial-scalewidth 都会有兼容性问题,所以设置布局视口为理想视口的最佳方法是同时设置这两个属性
  • 即使设置了 user-scalable = no,在 Android Chrome 浏览器中也可以强制启用手动缩放

如果 布局视口的宽度 = device-width(设备宽度,也就是:物理像素/dpr)时,此时页面 100% 的宽度正好能在视觉视口中完全显示,不需要缩放查看页面了,而且在不同尺寸下都能基本表现一致,此时的布局视口的状态我们就称为理想视口(Ideal Viewport)。

# 视觉视口

视觉视口是用户当前看到的区域,用户可以通过缩放操作视觉视口,同时不会影响布局视口。

视觉视口示意图

视觉视口和缩放比例的关系为:当前缩放值 = 理想视口宽度 / 视觉视口宽度

所以,当用户放大时,视觉视口将会变小,CSS 像素将跨越更多的物理像素。

# 理想视口

布局视口的默认宽度并不是一个理想的宽度,于是 Apple 和其他浏览器厂商引入了理想视口的概念,它对设备而言是最理想的布局视口尺寸。显示在理想视口中的网站具有最理想的宽度,用户无需进行缩放。

理想视口的值其实就是屏幕分辨率的值,它对应的像素叫做设备逻辑像素(Device Independent Pixel,DIP)。DIP 和设备的物理像素无关,一个 DIP 在任意像素密度的设备屏幕上都占据相同的空间。如果用户没有进行缩放,那么一个 CSS 像素就等于一个 DIP。

理想视口的宽度一般可以通过以下公式计算:理想视口的宽度 = 设备像素 / dpr

也就是当 布局视口的宽度 等于 设备独立像素的宽度 时就是理想视口。

用下面的方法可以使布局视口与理想视口的宽度一致:

<!-- 这行代码告诉浏览器,布局视口的宽度应该与理想视口的宽度一致 -->
<meta name="viewport" content="width=device-width" />

实际上,这就是响应式布局的基础。

# 自适应布局

自适应布局的特点是分别为不同的屏幕分辨率定义布局,即创建多个静态布局,每个静态布局对应一个屏幕分辨率范围。改变屏幕分辨率可以切换不同的静态局部(页面元素位置发生改变),但在每个静态布局中,页面元素不随窗口大小的调整发生变化。可以把自适应布局看作是静态布局的一个系列。

关键是需要找到一种长度单位,使得一样的取值,在不同尺寸的设备屏幕上按大小比例缩放。

移动端适配方案:

  • rem
  • rem + 媒体查询
  • vw/vh
  • rem + vw/vh
  • 百分比布局

# rem

rem 是 CSS3 新增的相对单位,与 em 不同,使用 rem 为元素设定字体大小时,相对的是 HTML 根元素。

这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。

对于不支持它的浏览器,应对方法也很简单,就是多写一个绝对单位的声明。这些浏览器会忽略用 rem 设定的字体大小。

# 适配原理

首先,rem 是根元素 <html> 标签的 font-size 字体大小决定的,它们的关系是:

$fontSize = 7.5px

1rem = 1 * $fontSize
2rem = 2 * $fontSize

设计师给的视觉稿通常都是 640px 或者 750px(这里指的是宽度),我们会约定一种尺寸的稿子作为参考去布局。

假如约定的是 750px 布局,那么第一件事情就是设置根元素 <html>font-size,那我们把 750px 分成 10 份,每份 $rem = 750px / 10 = 75px,也就是 1rem = 75px10rem = 750px

但是我们视觉稿上的单位是 px,我们需要转换成 rem,因为前面说了 1rem = 75px1px = 1/75rem,所以用视觉稿上的 px 值除以 75 就可以了,比如 100px = 100/75rem = 1.333rem

也可以借助预编译器:

/* 基准 font-size,可设置成其他值 */
$rem: 100;
@mixin px2rem($name, $px) {
    #{$name}: $px / $rem * 1rem;
}

// 使用示例
.container {
    @include px2rem(height, 240);
}

// 编译后
.container {
    height: 2.4rem;
}

# vw/vh

vw/vh 方案即将视觉视口宽度 window.innerWidth 和视觉视口高度 window.innerHeight 等分为 100 份。

vw/vh 方案和 rem 类似也是相当麻烦需要做单位转化,而且 px 转换成 vw 不一定能完整整除,因此有一定的像素差

不过在前端工程化的今天,Webpack 解析 CSS 的时候用 postcss-loader 有个 postcss-px-to-viewport (opens new window) 能自动实现 pxvw 的转化。

CSS Values and Units Module Level 3 (opens new window) 中和 Viewport 相关的单位有四个,分别为 vwvhvminvmax

  • vw:是 Viewport's width 的简写,1vw 等于window.innerWidth1%
  • vh:和 vw 类似,是 Viewport's height 的简写,1vh 等于 window.innerHeihgt1%
  • vminvmin 的值是当前 vwvh 中较小的值
  • vmaxvmax 的值是当前 vwvh 中较大的值

vminvmax 是根据 Viewport 中长度偏大的那个维度值计算出来的,如果 window.innerHeight > window.innerWidthvmin 取百分之一的 window.innerWidthvmax 取百分之一的 window.innerHeight 计算。

# 方案对比

布局方案 用户体验 兼容性 依赖 JS 支持超大屏幕 需要修正字体
rem iOS4.1 Android2.1
rem + 媒体查询 iOS4.1 Anrdoid2.1 x
vw/vh iOS6.1 Android4.4 x x x
rem + vw iOS6.1 Anroid.4.4 x

# 响应式布局

# 1px 边框问题

# 参考资料

上次更新: 2022/9/23 15:34:58