熟悉又陌生的移动端适配
什么是移动端适配
在不同尺寸的移动设备上,让页面达到合理的展示或者说等比缩放展示。
分辨率
- 逻辑分辨率:它是软件和硬件(物理分辨率)之间的一个转换层,可通过
window.screen.width
获取宽度,视口大小就是参照该值进行缩放。 - 物理分辨率:它是设备硬件固有的分辨率,出厂后就固定,可通过查看设备屏幕参数获取,也可通过
window.screen.width * window.devicePixelRatio
获取。
历代 iphone 分辨率:
设备 | 逻辑分辨率 | 物理分辨率 | dpr = 物理分辨率 / 逻辑分辨率 | PPI |
---|---|---|---|---|
iphone 3G | 320 x 480 | 320 x 480 | @1x | 163 |
iphone 4/4s | 320 x 480 | 640 x 960 | @2x | 163 |
iphone 5/5s | 320 x 568 | 640 x 1136 | @2x | 326 |
iphone 6/6s | 375 × 667 | 750 × 1334 | @2x | 326 |
iphone X/Xs | 375 × 812 | 1125 × 2436 | @3x | 458 |
像素密度 PPI
像素密度 PPI (Pixel Per Inch) 是指每英寸的长度(2.54 cm)中容纳的像素个数。
PPI问:一像素到底有多大?
答:1 像素长度 = 2.54 / PPI
例:1 像素长度(iphone 6) = 2.54 / 326 = 0.0779 mm = 头发直径(0.06 ~ 0.09 mm)
viewport 相关
概念
移动设备上的 viewport
就是设备屏幕上能用来显示网页的那一块区域,又叫「视口」,代码就是根据视口宽度进行排列布局的。但 viewport
又不仅仅是浏览器可视区域的大小,它可能比浏览器可视区域大,也可能比它小。默认情况下,移动设备的 viewport
要大于浏览器可视区域,这主要是用来兼容显示 PC 端的网站(iPhone 默认宽度 980px
)。
控制 viewport
通过 meta 标签控制 viewport 大小,下面代码是将 viewport 的宽度等于逻辑分辨率
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
width 和 initial-scale 的取值有冲突,width=device-width
与 initial-scale=1.0
是等效的,这两个值都可以设置初始视口大小,如果同时设置时在 Safari 和部分 Android 浏览器上会选择使用较大值。各个浏览器表现可能不一致,为了统一应该只使用其中一个值,使用 initial-scale=1.0
更方便计算。
视口宽度 = 逻辑分辨率 / initial-scale
可以通过 document.documentElement.clientWidth
来获取 viewport
的宽度(不包括滚动条)。
移动端适配的方式
所谓的移动端适配,就是说页面内的元素宽高,是根据不同逻辑分辨率自动改变的。那什么样的长度单位能够满足呢?答案是通过 vw 或 rem(需要 js 根据设备宽度计算得出值) 单位来达到适配的目的。
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
rem
兼容 IOS 4.1+, Android 2.1+ 可看这里[1]
通过 meta 标签设置视口的宽度等于设备宽度,如下:
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
rem
这个单位代表根元素(通常为 元素)的
font-size
大小,通过当前设备宽度来设置跟元素的字体大小,来达到移动端适配的目的。
一般是参照设计稿来进行开发,假如设计稿的宽度为 375px,那怎么通过 rem 来进行适配呢?
在不同尺寸的设备上,通过 js 按照设计稿的宽度等比例计算根元素的字体大小,这样才不会失真。
比如,设计稿的宽度为 375px 时的根字体大小为 100px 也就是 1rem,那当前设备下的根字体为多少?因为是等比缩放,所以通过如下等式可计算得出当前根字体(currentRootFontSize)值。
设计稿根字体大小(随意设定) / 设计稿宽度 = 当前根字体大小 / 视口宽度
因为:100px / designWidth = currentRootFontSize / viewportWidth
所以:currentRootFontSize = viewportWidth * 100px / designWidth
用代码可表示为:
const designWidth = 375 // 设计稿宽度
const viewportWidth = document.documentElement.clientWidth // 当前视口宽度
document.documentElement.style.fontSize = (viewportWidth / designWidth) * 100 + 'px' // 根字体大小
上面是设备像素比(dpr)为 1 的计算方式。但是移动适配往往存在 1 像素问题,因为 px 是逻辑像素,跟屏幕上的物理像素有可能不是 1:1 的关系,所以就有可能出现,1px 对应 2 个或多个物理像素的情况。
当 dpr > 1 时,同时想保证 1px 始终对应 1 个物理像素,那就需要让根字体扩大 dpr 的倍数,同时通过 meta 标签让视口缩小 1/dpr 倍即可。
例如:当 dpr = 2 时
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, user-scalable=0" />
优点:
- 使用 meta initial-scale 来缩放,解决了 1px 问题
缺点:
- 需要额外的 js 逻辑来控制根字体大小
- 逻辑不直观,需要根字体大小进行转换
- initial-scale 进行视口缩放后,第三方 UI 库组件显示有影响,需要额外处理
采用 rem 进行移动端适配,是个历史过程,因为早期 rem 兼容性比 vw 好,但现在而言,项目没有要求太高兼容性的情况下,完全可以使用 vw 进行移动端适配。
vw
1vw 等于视口宽度的 1%,它是天生的移动端适配单位,无需多余转换。
兼容 IOS 8+, Android 4.4+ 可看这里[2]
优点:
- 无需引入多余 js 逻辑
- 语义化,vw 逻辑清晰,不需要像 rem 那样需要换算一下
缺点:
- 1px 问题需要额外处理
react 项目应用
postcss-px-to-viewport[3] 是将 px 转换成视口单位(vw、vh)的 PostCss[4] 插件
// 项目中配置
addPostcssPlugins([
require('postcss-px-to-viewport')({
viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度
}),
]),
参考
- Window.devicePixelRatio[5]
- 移动前端开发之 viewport 的深入理解[6]
- vw 相比 rem,在实际开发中究竟有多大区别?[7]
- iPhone 屏幕分辨率和适配规则(基础篇)[8]
- 逻辑分辨率和物理分辨率到底是什么呀?[9]
参考资料
[1]可看这里: https://caniuse.com/#feat=rem
[2]可看这里: https://caniuse.com/#feat=viewport-units
[3]postcss-px-to-viewport: https://github.com/evrone/postcss-px-to-viewport/blob/master/README_CN.md
[4]PostCss: https://github.com/postcss/postcss
[5]Window.devicePixelRatio: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/devicePixelRatio
[6]移动前端开发之 viewport 的深入理解: https://www.cnblogs.com/2050/p/3877280.html
[7]vw 相比 rem,在实际开发中究竟有多大区别?: https://www.zhihu.com/question/37179916/answer/101810379
[8]iPhone 屏幕分辨率和适配规则(基础篇): https://www.jianshu.com/p/41a8ccdf91ed
[9]逻辑分辨率和物理分辨率到底是什么呀?: https://www.zhihu.com/question/40506180