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

移动端适配,经常遇到的问题。首先要分清 设备独立像素、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
表示每英寸中像素的数目,确切的说是像素密度,对于屏幕来说是指每英寸物理像素的数目及显示器设备的点距。数值越高,代表显示器的显示密度越高。

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

iPhone 11 Max pro
为例。

\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
。每块屏幕的物理像素多少在出厂时已经确定。 pt
CSS
单位中属于真正的 绝对单位
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。

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

换算关系

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

二、设备独立像素(DIP)

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

iOS
单位为 pt
Android
单位为 dp
ReactNative
未指明其实也是设备独立像素 dp

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

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

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

经常用 P 和 K 描述屏幕。

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

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

三、视口

视口( viewport)代表当前可见的计算机图形区域。在 Web浏览器术语中,通常与浏览器窗口相同,但不包括浏览器的 UI, 菜单栏等——即指你正在浏览的文档的那一部分。
一般我们所说的视口共包括三种:布局视口、视觉视口和理想视口,它们在屏幕适配中起着非常重要的作用。

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

Meta Viewport

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


看一下 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

.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
类似,将图片放在背景上。

.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 对线条缩放。

.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

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

@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
后才能使用这两个函数:


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

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

七、横竖屏适配的问题

7.1 JavaScript 检测横竖屏

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

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检测横屏

@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
来显示不同的背景图片。

.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

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

这种方式只适用于背景图

8.5 srcset

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


8.5 JS拼接图片 url

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

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

9.6 使用 svg

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