关于移动端适配的一些问题(笔记)

原文 《关于移动端适配,你必须要知道的》 - code秘密花园

前言

移动端适配,经常遇到的问题。首先要分清 设备独立像素、CSS像素、DPR、PPI、DIP 概念和区别。

  • 基础概念
  • 字体大小区别
  • 1px 问题
  • UI 图完美适配方案
  • iPhoneX 适配方案
  • 横屏适配
  • 高清屏图片模糊的问题

理解问题产生的原因,可以更好的解决问题。

一、基本概念

1.1 英寸(inch)

一般用英寸来描述屏幕的大小,如手机 5.7、6.5 等使用的单位都是英寸。

屏幕的尺寸指定是对角线的长度。

1.2 分辨率

分辨率一般分为两种:屏幕分辨率和图片分辨率。

屏幕分辨率是指一块屏幕上有多少个像素点。如:iPhone 11 Max Pro 的分辨率是 2688 x 1242,表示的是屏幕在水平和垂直方向上像素点数。

图像分辨率是指图片含有的像素数,比如一张分辨率为 1080*768,表示图片在垂直和水平方向上所具备的像素点数。

1.3 每英寸像素(PPI)

PPI 表示每英寸中像素的数目,确切的说是像素密度,对于屏幕来说是指每英寸物理像素的数目及显示器设备的点距。数值越高,代表显示器的显示密度越高。

1
PPI = \frac{\sqrt{水平像素点数^2+垂直像素点数^2}}{尺寸}

iPhone 11 Max pro 为例。

1
\frac{\sqrt{2688^2+1242^2}}{6.5} ≈ 455 

那么屏幕每英寸约有 455 个物理像素点。实际尺寸不到6.5。

1.4 DPI

DPI:每英寸包含的点数,是一个抽象单位,可以是屏幕像素点、图片像素点也可以是打印机墨点。当 DPI 描述屏幕和图片时与 PPI 等价。

1.5 设备独立像素(DIP)

设备独立像素,也称为逻辑像素,简称 DIP。

用来告诉不同分辨率的手机,它们在界面上显示元素的大小是多少。

1.6 设备像素(DP)

设备像素(Device Pixels,px),又称为物理像素,是指显示屏上的一个个物理点。物理像素的点大小是固定的,单位是pt。每块屏幕的物理像素多少在出厂时已经确定。ptCSS 单位中属于真正的绝对单位1pt = 1/72(inch) 一英寸等于2.54厘米。例如:iPhone 11 Max pro 2688 x 1242 像素分辨率,458 ppi

1.7 CSS像素(PX)

CSS像素 (CSS Pixel, px),又称为虚拟像素。就是Web中使用的像素,单位是 px。 在 CSS 规范中,长度分为两类,绝度单位和相对单位。这里 px 是一个相对单位,相对的是设备像素。

CSS像素是图像显示的基本单元,是一个相对量,是一个抽象的概念。所以在说CSS像素大小的时候要考虑上下文的不同。

由于不同的设备的物理像素是不同的,所以 CSS 认为浏览器会对 CSS 中的像素进行调节,使得浏览器中 1 CSS 像素的大小在不同的设备上看起来差不多,目的是为了保证阅读体验。

在 DPR 为 1的屏幕中,浏览器可视窗口 800px 里面有个 div 为 400px ,此时将页面放大 200%,发现 div 宽度占满这个浏览器。也就是说默认一个 CSS 像素等于一个物理像素宽度,放大后一个CSS像素等于两个物理像素宽度。所以,可以看出 CSS 像素是一个相对值。

1.8 设备像素比 (DPR)

在为缩放状态下,设备像素和CSS像素之间的比例关系。苹果的 Retina 屏的DPR为2,也就是说2*2个物理像素表示一个CSS像素。

在 web 中,浏览器通过 window.devicePixelRatio 来获取 DPR。
在 CSS 中,使用媒体查询min-device-pixel-ratio,区分 DPR。

1
@media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2){}

换算关系

1
DPR = \frac{设备像素}{设备独立像素}

二、设备独立像素(DIP)

iOS 、Android 和 ReactNative 中使用的都是设备独立像素。

iOS 单位为 ptAndroid 单位为 dpReactNative未指明其实也是设备独立像素dp

为了适配屏幕,需要将物理像素转为设备独立像素。例如:给定一个元素的高度 200px (此时px表示物理像素而不是CSS像素),iPhone 6的设备像素比为2,那么给定的元素高度应该是 200px/2 = 100dp

在 Web 开发中,使用的是CSS像素,当页面的缩放比为100%时,一个CSS像素等于一个设备独立像素。
当页面放大是,CSS像素也会被放大,一个CSS像素跨越多个设备独立像素。

1
页面缩放比 = \frac{CSS像素}{设备独立像素}

经常用 P 和 K 描述屏幕。

P代表的是屏幕纵向的像素个数,1080p即纵向上有1080个像素点,分辨率为 1920 * 1080的屏幕就属于 1080P 屏幕。

K代表屏幕横向有几个1024个像素,一般来将横向屏幕超过 2048 就属于 2K 屏,超过 4096 就属于 4K 屏。

三、视口

视口( viewport)代表当前可见的计算机图形区域。在 Web浏览器术语中,通常与浏览器窗口相同,但不包括浏览器的 UI, 菜单栏等——即指你正在浏览的文档的那一部分。

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

  • 布局视口:是网页布局的基准窗口。
  • 视觉视口:用户在屏幕中真是看到的窗口。
  • 理想视口:网页在移动展示的理性大小。

Meta Viewport

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

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

看一下 viewport 的配置含义。

名称 可选值 描述
width 正整数、device-width 定义布局视口的宽度,单位px
height 正整数、device-height 定义布局视口的高度,单位px
inital-scale 0 - 10.0 定义页面初始缩放比率
minimum-scale 0-10.0 定义缩放的最小值,必须小于或等于maximum-scale
maximum-scale 0-10.0 定义缩放的最大值
user-scalable yes、no 是否允许缩放
viewport-fit contain、cover 适配iphoneX等屏幕

移动适配,为了使页面获得更好的显示效果,让布局视口、视觉视口尽可能等于理想视口。

device-width 就是等于理想视口的宽度。由于 inital-scale=理性视口宽度/视觉视口宽度,所以 initial-scale=1时,理性视口等于视觉视口。

这是,一个 CSS像素等于一个设备独立像素,且是基于理想视口布局的,所以呈现出的效果在各设备上大致相似。

四、字体大小 px、em、rem、pt

4.1 px

回忆一下 px 像素是相对值,相对于屏幕的分辨率而言的。

1px是多少物理像素呢?需要先知道每英寸像素数DPI,Windows默认是 96dpi,Apple默认是 72dpi。

任意的浏览器的默认字体是 16px。

单位换算,默认情况下 16px = 1em = 1rem。

4.2 em

em 是指相对于父元素的字体大小的单位。相对字符宽度的倍数,类似于百分比。浏览器默认 1em = 16px。

默认浏览器符合:16px = 1em,12px = 0.75em, 10px = 0.625em。如设置 body 选择器的 font-size=62.5%,使1em = 10px, 1.2em = 12px。这样方便计算。

4.3 rem

rem 是相对于根元素的字体大小的单位,它是CSS3新增的相对单位。与 em 的区别在于前者相对父元素字体大小后者相对根元素自字体大小。

单位换算,根元素字体16px: 16px = 1rem;16px * 0.75 = 12px => 0.75rem = 12px。

4.4 pt

pt(磅): 是一个物理长度单位,指1/72英寸。

pt = 1/72(英寸),px = 1/dpi(英寸) => pt = px * 72/dpi

在 windows 下以96dpi来计算,pt = px * 72/96 = px * 3/4 及默认 16px = 12pt。

五、1px 边框问题

为了适配屏幕使用设备独立像素来对页面布局。在DPR大于1的屏幕上,1px实际上被多个物理像素渲染,这就出现 1px 在屏幕上看起来很粗的原因。

5.1 border-image

基于 media 查询判断不同的 DPR 给定不同的 border-image

1
2
3
4
5
6
7
8
9
10
.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;
}
}

5.2 background-image

border-image类似,将图片放在背景上。

1
2
3
4
5
6
7
8
9
.border_1px {
border-bottom: 1px solid #000;
}
@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;
}
}

上面两种缺点是无法添加圆角。

5.3 伪类 + transform

基于 media 获取 DPR 对线条缩放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.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)
}
}

可以添加圆角,只需为伪类添加 border-radius即可。

5.4 svg

借助 PostCSSpostcss-write-svg直接使用 border-image 和 background-image 创建 svg 的 1px 边框。

1
2
3
4
5
6
7
8
9
10
11
12
@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;
}

推荐使用这种方法。

六、适配iPhoneX屏幕

iPhoneX 出现取消了物理按键,改成了底部的小黑条,但是这样的改动给前端适配增加了难度。

6.1 安全区域

iPhoneX屏幕外观三个改动:圆角、刘海和小黑条。为了适配诞生了安全区域的概念,它是一个不受上面三个改动的影响的可视窗口。

6.2 viewport-fit

前面 viewport 中有个 viewport-fit=cover,这个属性专门为 iPhoneX 诞生的属性,用于限制网页如何在安全区域内展示。

  • contain: 可视窗口完全包含网页内容
  • cover:网页内容完全覆盖可视窗口

默认情况下或者设置为 auto 和 contain效果相同。

6.3 env 和 constant

我们需要将顶部和底部合理的摆放在安全区域内, iOS11新增了两个 CSS函数 env、constant,用于设定安全区域与边界的距离。

函数内部可以是四个常量:

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

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

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

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

1
2
3
4
body {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}

七、横竖屏适配的问题

7.1 JavaScript 检测横竖屏

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

1
2
3
4
5
6
7
8
9
10
window.addEventListener('resize', () => {
if (window.orientation === 180 || window.orientation === 0) {
// 正常方向或者旋转180度
console.log('竖屏')
}
if (window.orientation === 90 || window.orientation === -90) {
// 顺时针或逆时针 90 度
console.log('横屏')
}
})

7.2 CSS检测横屏

1
2
3
4
5
6
@media screen and (orientation: portrait) {
// 竖屏
}
@media screen and (orientation: landscape) {
// 横屏
}

八、图片模糊问题

8.1 原因

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

理论上,一个像素点有一个物理像素点渲染效果最佳。但是在 dpr > 1 的屏幕中,一个位图像素点由多个物理像素点渲染,这些物理像素点不能准确的分配颜色,只能去近似值,所以相同的图片在 dpr > 1 的屏幕上模糊了。

8.2 解决方案

为了保证图片质量,可以在不同的 DPR 屏幕中显示不同分辨率的图片。

如:DPR = 2 的屏幕显示二倍图(@2x),在DPR = 3的屏幕中显示三倍图(@3x)。

8.3 media 查询

使用 media 查询来判断不同的 DPR 来显示不同的背景图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
.avatar {
background-image: url(xxx.png);
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.avatar {
background-image: url(xxx_2x.png);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 3) {
.avatar {
background-image: url(xxx_3x.png);
}
}

这种方式只适用于背景图

8.4 image-set

1
2
3
.avatar {
background-image: -webkit-image-set('xxx.png' 1x, 'xxx_2x.png' 2x);
}

这种方式只适用于背景图

8.5 srcset

使用 img 标签的 srcset 属性,浏览器会自动根据像素密度匹配最佳图片显示。

1
<img src="xxx.png" srcset="xxx_2x.png 2x, xxx_3x.png 3x">

8.5 JS拼接图片 url

使用 window.devicePixelRatio 获取 DPR,遍历所有图片,替换图片地址。

1
2
3
4
5
const dpr = window.devicePixelRatio
const images = document.querySelectorAll('img')
images.forEach(item => {
item.src.replace('.', `@${dpr}x.`)
})

9.6 使用 svg

SVG的全称是可缩放矢量图。不同于位图使用像素,SVG使用向量描述图片。它无论如何缩放都不会失真。除了用代码绘制 svg ,还可以像使用位图一样使用 svg 图片。