移动端H5的屏幕适配
移动端H5的屏幕适配
作者:何志勇
一、相关概念
1.1 屏幕相关
屏幕尺寸(单位:英寸)
- 计算方式:测量屏幕对角线的长度,以1英寸 = 2.54 厘米进行转化。
- 发展趋势:从初代iPhone3.5英寸开始,屏幕尺寸越来越大,到达5寸以上成为主流,单手握持已较为困难;然后全面屏封装工艺的进步,成功跨越6英寸;未来柔性屏幕普及使得更大的手持屏幕成为可能。
屏幕分辨率
- 概念:指屏幕硬件实际由多少个
物理像素点
(真实的物理单元)组成 - 计算方式:以水平乘以垂直像素数来计算,常用分辨率1280x720(720p)、1920x1080(1080p,通常所说的高清屏),屏幕横向像素达到2048以上通常成为2K,4096以上称为4K。
- 发展趋势:目前中低端机型主流仍是1080p屏幕,旗舰手机分辨率主流是2K屏幕。
像素密度(PPI: Pixel Per Inch)
- 概念:指每英寸包括的像素数,是衡量屏幕的清晰度或者图片质量的参数
- 计算方式:
。
- 说明:ppi在120-160之间的手机被归为低密度手机,160-240被归为中密度,240-320被归为高密度,320以上被归为超高密度(例如苹果公司的
Retina屏幕
),例如iPhone 6
的PPI
为 326,它每英寸约含有326个物理像素点
。
1.2 像素
设备像素(Device Pixel)
设备像素
也称物理像素
,是屏幕硬件设备能控制显示的最小单位,操作系统可以为每个物理像素
自己的颜色和亮度,每个物理像素
的大小是屏幕固有的属性,屏幕出厂以后就不会改变了。
设备独立像素(Device Independent Pixel)
设备独立像素
是操作系统定义的一种像素单位,也称逻辑像素
。操作系统在屏幕屏幕硬件支持基础上,通过设备独立像素调节最佳的显示比例。
在PC端,几乎所有的图形化操作系统都支持手动调整逻辑像素
。
但在移动端,我们很少关注到相关设置项,其实是因为只是使用了Android
或iOS
默认设置。
设备像素比(DPR: Device Pixel Ratio)
设备像素比
简称为dpr,是设备像素
和设备独立像素
的比值,计算公式为:设备像素比 = 设备像素/设备独立像素
获取方式:
- JS中,浏览器为我们提供了
window.devicePixelRatio
来帮助我们获取dpr
。 - CSS中,可以使用媒体查询min-device-pixel-ratio,区分
dpr
:
@media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2){ }
React Native
中,可以使用PixelRatio.get()来获取dpr
位图像素
一个位图像素
是栅格图像(如:png, jpg, gif等)最小的数据单元。每一个位图像素
都包含着一些自身的显示信息(如:显示位置,颜色值,透明度等)。
CSS像素
CSS像素
是一个抽象的单位,主要使用在浏览器上,用来精确度量Web页面上的内容。
当页面缩放比例为100%时,一个CSS像素等于一个设备独立像素,当用户对浏览器进行了放大,CSS像素会被放大,这时一个CSS像素会跨越更多的物理像素。
在chrome
开发者工具中,可以模拟web页面在手机端的显示比例,每种型号上面会显示一个尺寸,比如iPhone 6显示的尺寸是375x667,实际iPhone 6的实际分辨率为1334x750
,这里显示的就是设备独立像素
,预设的设备像素比
为2。
1.3 Retina屏幕
移动端智能手机发展迅速,型号更为丰富。从早期的320x480
,到现在1080p
手机全面普及,屏幕分辨率、像素密度、屏幕比例一直都存在着一些差异。倘若我们都以实际的物理分辨率(物理像素做为单位)
去做开发,理论上在尺寸相近机型中,分辨率的越高的手机页面元素会变得越来越小,类似于下图中的显示效果。
而现实情况是,我们的智能手机,不管分辨率多高,他们所展示的界面比例都是基本类似的。乔布斯在iPhone4
的发布会上首次提出了Retina Display(视网膜屏幕)
的概念,它正是解决了上面的问题,这也使它成为一款跨时代的手机,后续发布的智能手机,都采用了类似的适配方案。
在iPhone4
使用的视网膜屏幕中,把2x2
个像素当1个像素使用,其实也就是前面提到的设备独立像素
的在手机端的应用,这样让屏幕看起来更精致,但是元素的视觉大小却不会改变。如下图所示,在水平方向上,白色手机会用300个物理像素去渲染它,而黑色手机实际上会用600个物理像素去渲染它,黑色手机实际显示效果更为细腻,而两个手机中元素的大小不会有明显差异。
1.4 视窗(viewport)
视窗(viewport)
在桌面浏览器中,是用来显示我们的网页的那一块区域。但在手机浏览器(或者App中的WebView)
中,由于正常使用时横纵比例恰好和桌面浏览器相反,所以viewport
相对较窄,为了能更好为CSS布局服务,所以提供了三个viewport
: 布局视窗(layout viewport)
、视觉视窗(visual viewport)
和理想视窗(ideal viewport)
。
布局视窗(layout viewport)
布局视窗(layout viewport)
:当我们以百分比来指定一个元素的大小时,它的计算值是由这个元素的包含块计算而来的。当这个元素是最顶级的元素时,它就是基于布局视窗来计算的。所以,布局视窗是网页布局的基准窗口。
在PC浏览器中,布局视窗就等于当前浏览器的窗口大小(不包括borders
、margins
、滚动条)。
在移动端浏览器中,布局视窗被赋予一个默认值,大部分为980px,这保证PC的网页可以在手机浏览器上呈现,但是非常小,用户可以手动对网页进行放大。
我们可以通过调用document.documentElement.clientWidth / clientHeight
来获取布局视窗大小。
视觉视窗(visual viewport)
视觉视窗(visual viewport)
:用户通过屏幕真实看到的区域,默认等于当前浏览器的窗口大小(包括滚动条宽度)。
当用户对浏览器进行缩放时,不会改变布局视窗的大小,所以页面布局是不变的,但是缩放会改变视觉视窗的大小。
例如:用户将浏览器窗口放大了200%,这时浏览器窗口中的CSS
像素会随着视觉视窗的放大而放大,这时一个CSS像素
会跨越更多的物理像素。
所以布局视窗
会限制你的CSS布局
,而视觉视窗
决定用户具体能看到什么。
我们可以通过调用window.innerWidth / innerHeight
来获取视觉视窗大小。
理想视窗(ideal viewport)
布局视窗在移动端展示的效果并不是一个理想的效果,所以理想视窗(ideal viewport)
就诞生了:网站页面在移动端展示的理想大小。
如上图,在浏览器调试移动端时页面上给定的像素大小就是理想视窗大小,它的单位正是设备独立像素。
当页面缩放比例为100%时,CSS像素 = 设备独立像素,理想视窗 = 视觉视窗。
我们可以通过调用screen.width / height
来获取理想视窗大小。
meta标签
<meta>
标签有很多种,而这里要着重说的是viewport
的meta标签
,它可以告诉浏览器如何规范的渲染Web页面。在移动端为了得到更好的展示效果,通常需要设置meta标签如下:
<meta name="viewport" content="width=device-width; initial-scale=1; maximum-scale=1; minimum-scale=1; user-scalable=no;">
content中参数的含义如下:
Value | 可能值 | 描述 |
width | 正整数或device-width | 以pixels(像素)为单位, 定义布局视窗的宽度。 |
height | 正整数或device-width | 以pixels(像素)为单位, 定义布局视窗的高度。 |
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) | 如果设置为 no,用户将不能放大或缩小网页。默认值为 yes。 |
移动端配置
为了在移动端让页面获得更好的显示效果,我们必须让布局视窗、视觉视窗都尽可能等于理想视窗。
device-width
就等于理想视窗的宽度,所以设置width=device-width
就相当于让布局视窗等于理想视窗。
由于initial-scale = 理想视窗宽度 / 视觉视窗宽度
,所以我们设置initial-scale=1;
就相当于让视觉视窗等于理想视窗。
这时,1个CSS像素
就等于1个设备独立像素
,而且我们也是基于理想视窗来进行布局的,所以呈现出来的页面布局在各种设备上都能大致相似。
缩放
上面提到width
可以决定布局视窗的宽度,实际上它并不是布局视窗的唯一决定性因素,设置initial-scale
也有肯能影响到布局视窗,因为布局视窗宽度取的是width
和视觉视窗宽度的最大值。
例如:若手机的理想视窗宽度为400px
,设置width=device-width,initial-scale=2
,此时视觉视窗宽度 = 理想视窗宽度 / initial-scale即200px,布局视窗取两者最大值即device-width 400px。
若设置width=device-width,initial-scale=0.5
,此时视觉视窗宽度 = 理想视窗宽度 / initial-scale
即800px
,布局视窗取两者最大值即800px
。
浏览器窗口API
浏览器为我们提供的获取窗口大小的API有很多,下面我们再来对比一下:
window.innerHeight
:获取浏览器视觉视窗高度(包括垂直滚动条)。window.outerHeight
:获取浏览器窗口外部的高度。表示整个浏览器窗口的高度,包括侧边栏、窗口镶边和调正窗口大小的边框。window.screen.Height
:获取获屏幕取理想视窗高度,这个数值是固定的,设备的分辨率/设备像素比
window.screen.availHeight
:浏览器窗口可用的高度。document.documentElement.clientHeight
:获取浏览器布局视窗高度,包括内边距,但不包括垂直滚动条、边框和外边距。document.documentElement.offsetHeight
:包括内边距、滚动条、边框和外边距。document.documentElement.scrollHeight
:在不使用滚动条的情况下适合视窗中的所有内容所需的最小宽度。测量方式与clientHeight
相同:它包含元素的内边距,但不包括边框,外边距或垂直滚动条。
二、适配方案
前面提到的Retina屏幕
的显示方案,已经在一定程度上对不同分辨率显示效果进行了适配,那么开发者还需要做其它的适配吗?答案是肯定的,Android
生态手机屏幕尺寸非常多、分辨率高低跨度非常大,不像苹果只有它自己的几款固定设备、尺寸,即便是同为ios生态,预设的设备像素比
也不尽相同,还有一些不能忽视的高清屏幕显示上的差异性问题(1px、图片模糊)。所以,为了保证各型号手机的更好显示效果,针对性的做适配开发还是比较重要的。
移动端布局适配的初衷,是最大程度在各个尺寸屏幕上还原设计稿。当然设计稿一般只会出一个基准尺寸,我们要做的其实是用相似的布局比例,让页面内容在各个尺寸屏幕上都能合理显示,与此同时也要最大程度的减少开发中的尺寸单位人工计算。
以下适配示例均采用UI设计常用的机型iPhone 6
作为基准设计尺寸,iPhone 6
的物理分辨率
为750 x 1334
,设备像素比
为2
,则对应开发中设备独立像素
为375 x 667
。
注意:以下几种适配方案使用的单位
并不适合用到段落文本上,对于文本内容的适配原则,应该是保持阅读舒适性的基础上,屏幕尺寸越大,一次性展示的文字内容越多。
2.1 rem方案
rem
是相对于html
节点的font-size
来做计算的一个相对单位,在运行时
动态修改html
节点的font-size
大小,从而影响所有使用rem
单位的布局。
1rem该设为多少?其实没有什么统一的标准。国内主流网站也都在使用rem
方案,具体方案和阿里的大同小异,根据是否缩放viewport
分为两个阵营:
不缩放viewport(屏幕宽度为375px
):
- 马蜂窝 1rem = 37.5px
- 小米 1rem = 52.0833px
- 小红书 1rem = 50px
缩放viewport(屏幕宽度为375px
,viewport scale为0.5
): - B站 1rem = 46.875px
- 搜狐 1rem = 75px
最早的rem方案是阿里早期提出并在手机淘宝上使用,同期封装开源了lib-flexible适配库,下面以flexible
的方案为例介绍:
首先约定将设备的布局视窗
进行10
等分,将html
节点的font-size
设置为页面布局视窗
的1/10
,即1rem
就等于页面布局视窗
的1/10
,这就意味着我们后面使用的rem
都是按照页面
比例来计算的。
以iPhone6
为例:布局视窗为375px
,则1rem = 37.5px
,这时UI给定一个元素的宽为75px
,我们只需要将它设置为75 / 37.5 = 2rem
。
接下来要做的就是,根据设备的独立像素比
,在运行时
控制根节点font-size
的大小,使用css或者js都可以实现:
一种是媒体查询,优点:不需要额外使用js
去更改html
的字体,缺点:不连续,或者说并能完全实现对所有设备的布局规范统一;
html {
font-size: 50px
}
@media only screen and (min-device-width: 375px) and (-webkit-min-device-pixel-ratio: 2) {
html {
font-size: 37.5px
}
}
@media only screen and (min-device-width: 360px) and (-webkit-min-device-pixel-ratio: 3) {
html {
font-size: 36px
}
}
2
3
4
5
6
7
8
9
10
11
12
13
另一种是js
动态更改html
字体,优点:连续;缺点:不如直接写媒体查询的体验好;
// set 1rem = viewWidth / 10
function setRemUnit () {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
window.onload = function(){
setRemUnit()
};
window.onresize = function(){
setRemUnit()
};
2
3
4
5
6
7
8
9
10
11
该方案是阿里手淘早期使用,并封装开源了lib-flexible库,它的主要思想有三点:
- 根据
dpr
的值来修改viewport
实现1px的线 - 根据
dpr
的值来修改html
的font-size
,从而使用rem
实现等比缩放 - 使用
Hack
手段用rem
模拟vw
特性
存在的问题: - 需要额外
内联引入
一个lib-flexible库,保证在所有资源加载之前执行这个JS,使用iframe引用就会出现问题。 html
的font-size
设置到12px以下还是会按照12px=1rem来计算,这样所有使用了rem单位的尺寸都是错的。- 在奇葩的
dpr
设备上表现效果不太好,比如一些华为的高端机型 用rem布局会出现错乱。
2.2 vw方案
大漠老师的文章中提到,flexible+rem
只是当时的一个过渡方案,已经可以放弃使用,不管是现在的版本还是以前的版本,都存有一定的问题。它本质上只能算作一种Hack
手段,是在用rem
模拟vw
特性,因为早期的vw
一直存在兼容性不足的问题。
现在在2019年这个时间点上,我们看到viewport
单位的浏览器支持程度已达96.26%,考虑到主流浏览器兼容性较好,我们是时候使用viewport
单位大展身手了。
viewport
单位有以下几种:
vw(viewport's width)
:1vw
等于视觉视口的1%
vh(viewport's height)
:1vh
为视觉视口高度的1%
vmin
: vw 和 vh 中的较小值vmax
: 选取 vw 和 vh 中的较大值
如果视觉窗口为375px
,那么1vw = 3.75px
,这时UI给定一个元素的宽为75px(设备独立像素),我们只需要将它设置为75 / 3.75 = 20vw
。
这里的比例关系我们也不用自己换算,如果你使用vscode
,px2vw、px-to-vw这样的插件还是能找到的,效果如下:
另外我们还可以使用postCSS
的postCSS-px-to-viewport
插件帮我们完成这个过程。根据设计稿的默认大小,我们在postCSS
的配置文件中添加一些配置:
module.exports = {
plugins: {
'autoprefixer': {},
'postcss-px-to-viewport': {
viewportWidth: 750, // 视窗的宽度,对应的是我们设计稿的宽度,一般是750
viewportHeight: 1334, // 视窗的高度,根据750设备的宽度来指定,一般指定1334,也可以不配置
unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
selectorBlackList: ['.ignore', '.hairlines'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
mediaQuery: false // 允许在媒体查询中转换`px`
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
以750px宽度的视觉设计稿为例,那么100vw = 750px
,即1vw = 7.5px
。那么我们可以根据设计图上的px
值直接转换成对应的vw
值。在实际撸码过程,不需要进行任何的计算,直接在代码中写px,对于不需要自动转换单位的样式,可以在对应的元素html标签中添加配置中指定的类名.ignore
。
<div class="test ignore"></div>
.test {
width: 75px;
border-bottom-width: 4px;
line-height: 20px;
}
.ignore {
margin: 10px;
background-color: red;
}
2
3
4
5
6
7
8
9
编译出来的CSS为:
.test {
width: 10vw;
border-bottom-width: .533vw;
line-height: 2.667vw;
}
.ignore {
margin: 10px;
background-color: red;
}
2
3
4
5
6
7
8
9
当然,没有一种方案是十全十美的,vw同样有一定的缺陷:
px
转换成vw
不一定能完全整除,因此有一定的像素差。- 当容器使用
vw
,margin
采用px
时,很容易造成整体宽度超过100vw
,从而影响布局效果。当然我们也是可以避免的,例如使用padding
代替margin
,结合calc()
函数使用等等... - 在
Android 4.4
之下和iOS8
以下的版本都存有一定的问题,如果一定要考虑兼容这些设备,viewport-units-buggyfill也可以帮你解决兼容问题。
2.3 微信小程序的适配方案
微信小程序
中的引入了rpx作为动态单位,它的定义和实现思路与上述两种方案很相似。
首先,我们依据常规设计稿750
宽度,约定我们要适配的所有设备的屏幕基准宽度
都是750rpx
,这里的rpx
是相对于基准宽度
的单位。如在 iPhone6
上,屏幕宽度为375px
,共有750个物理像素
,则750rpx = 375px = 750物理像素
,1rpx = 0.5px = 1物理像素
。其它设备的单位换算关系如下:
该方案的优点是,可以直接使用设计稿上的单位长度开发,不必人工进行单位转换,完全由小程序运行时根据设备类型动态进行单位转换。
2.4 适配iPhoneX
安全区域(safe area)
核心内容应该处于 Safe area 确保不会被设备圆角(corners)
,传感器外壳(sensor housing,齐刘海)
以及底部的Home Indicator
遮挡。也就是说 我们设计显示的内容应该尽可能的在安全区域内;
viewport-fit
在iOS 11中采用了viewport-fit
的meta
标签作为适配方案, 它用于限制网页如何在安全区域内进行展示。
viewport-fit
的设置如下:
contain
:布局视窗
和视觉视窗
被设置为最大的矩形,viewport
完全包含网页内容,在viewport
之外的UA绘制的是未定义的,它可能是画布的背景色,或者UA认为合适的其他东西。cover
:布局视窗
和视觉视窗
被设置为设备物理屏幕的限定矩形,如上图2所示,网页内容完全覆盖可视窗口。auto
: 默认设置auto
,和contain
效果一致。
env、constant
我们需要将顶部和底部合理的摆放在安全区域内,iOS11新增了两个CSS函数env
、constant
,用于设定安全区域与边界的距离。
函数内部可以是四个常量:
safe-area-inset-left
:安全区域距离左边边界距离safe-area-inset-right
:安全区域距离右边边界距离safe-area-inset-top
:安全区域距离顶部边界距离safe-area-inset-bottom
:安全区域距离底部边界距离
在设置viewport-fit=cove
的基础上,constant
在iOS < 11.2
的版本中生效,env
在iOS >= 11.2
的版本中生效,这意味着我们往往要同时设置他们,将页面限制在安全区域内,兼容版本CSS代码如下:
.iPhoneX {
padding-top: constant(safe-area-inset-top); //为导航栏+状态栏的高度 88px
padding-top: env(safe-area-inset-top); //为导航栏+状态栏的高度 88px
padding-left: constant(safe-area-inset-left); //如果未竖屏时为0
padding-left: env(safe-area-inset-left); //如果未竖屏时为0
padding-right: constant(safe-area-inset-right); //如果未竖屏时为0
padding-right: env(safe-area-inset-right); //如果未竖屏时为0
padding-bottom: constant(safe-area-inset-bottom); //为底下圆弧的高度 34px
padding-bottom: env(safe-area-inset-bottom); //为底下圆弧的高度 34px
}
2
3
4
5
6
7
8
9
10
2.5 横屏适配
手机方向旋转,会导致横竖屏长度互换,页面显示比例当然也会发生变化,所以需要针对特定场景做出适配。
- js可以通过
window.orientation
获取屏幕旋转方向,屏幕旋转后页面宽高发生变化,会触发resize
事件。
window.addEventListener("resize", ()=>{
if (window.orientation === 180 || window.orientation === 0) {
// 正常方向或屏幕旋转180度
console.log('竖屏');
};
if (window.orientation === 90 || window.orientation === -90 ) {
// 屏幕顺时钟旋转90度或屏幕逆时针旋转90度
console.log('横屏');
}
});
2
3
4
5
6
7
8
9
10
- CSS也可以检测到屏幕旋转
@media screen and (orientation: portrait) {
/*竖屏...*/
}
@media screen and (orientation: landscape) {
/*横屏...*/
}
2
3
4
5
6
三、其它常见问题
3.1 1px问题
产生原因:目前移动设备普遍的设备像素比为2或3,1pxCSS像素
实际渲染为2(3)物理像素
,所以视觉效果
比较粗。注意这里是视觉效果比较粗,我们谈到视觉效果时这里的衡量单位就不再是像素
,而应该是cm
、mm
甚至μm
,因为无论多小的物理像素
都是有自己的宽和高的,由前面的屏幕放大我们观察到,像素单元的排列也不是特别紧密相接的,也就是说Retina屏幕
上的2(3)个物理像素
点宽度再加上它们之间的缝隙宽度的总和,完全可能大于正常屏幕上真正1个物理像素
点的宽度,所以有些屏幕显示1px视觉较粗也就不奇怪了。
解决方案:
- 伪元素 + transform,示例如下:
.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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 使用
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/1px_line.png) 0 0 2 0 stretch;
}
}
2
3
4
5
6
7
8
9
10
- 使用
background-image
,示例如下:
.border_1px {
border-bottom: 1px solid #000;
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
.border_1px{
background-image: url(../img/1px_line.png) repeat-x left bottom;
background-size: 100% 1px;
}
}
2
3
4
5
6
7
8
9
- 使用
SVG
作为border-image
或者background-image
,从大漠老师文章中得知此方案,需要借助PostCSS
的postcss-write-svg
插件,项目中有使用PostCSS
的,可考虑采用。
@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;
}
2
3
4
5
6
7
8
9
10
11
12
13
- 编译后:
.example { border: 1px solid transparent; border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E") 2 2 stretch; }
- 注意: 直接将宽设为
0.5px
,在iOS8
以下以及众多Android
机型上的都是无效的,兼容效果很不理想,不建议使用这种解决方案。
3.2 图片模糊问题
产生原因:我们平时使用的图片(png、jpg等格式
)都属于位图图像,位图由一个个像素点构成的,每个像素都具有特定的位置和精确的颜色值。
理论上,位图的每个像素对应在屏幕上使用一个物理像素来渲染,才能达到最佳的显示效果。
而在dpr > 1
的屏幕上,位图的一个像素可能由多个物理像素来渲染,由于单个位图像素不可以再进一步分割,所以只能就近取颜色的近似值,导致图片看起来比较模糊。
解决方案:为了保证图片质量,我们应该尽可能让一个物理像素
来渲染一个图片像素
,所以,针对不同dpr
的屏幕,我们需要展示不同分辨率的图片。
如:在dpr=2
的屏幕上展示两倍图(@2x)
,在dpr=3
的屏幕上展示三倍图(@3x)
。
- CSS背景图,可以使用
media
查询判断不同的设备像素比来显示不同精度的图片,示例如下:
.avatar{
background-image: url(conardLi_1x.png);
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
.avatar{
background-image: url(conardLi_2x.png);
}
}
@media only screen and (-webkit-min-device-pixel-ratio:3){
.avatar{
background-image: url(conardLi_3x.png);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- CSS背景图,还可以使用
image-set
直接设置多倍图片,示例如下:
.avatar{
background-image: -webkit-image-set( "conardLi_1x.png" 1x, "conardLi_2x.png" 2x );
}
2
3
img
标签引入的图片,可以通过设置srcset
属性,浏览器会自动根据像素密度匹配最佳显示图片,示例如下:
<img src="conardLi_1x.png" srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x">
- 使用JS根据获取的
设备像素比
,批量全局替换掉所有图片src,
const dpr = window.devicePixelRatio;
const images = document.querySelectorAll('img');
images.forEach((img)=>{
img.src.replace(".", `@${dpr}x.`);
})
2
3
4
5
- 最后,如果UI设计支持导出
SVG
素材,可以使用SVG
代替失真图片,SVG
全称是可缩放矢量图,属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。SVG
适用于img标签
引入和CSS
引入,示例如下:
<style>
.avatar {
background: url(conardLi.svg);
}
</style>
<img src="conardLi.svg">
<img src="data:image/svg+xml;base64,[data]">
2
3
4
5
6
7
参考
- https://juejin.im/post/5cddf289f265da038f77696c#heading-45
- https://www.w3cplus.com/CSS/vw-for-layout.html
- https://www.cnblogs.com/2050/p/3877280.html
小结
通过本文的介绍,你应该对移动端H5的适配有了整体的认识,至少能掌握一种完整的解决方案,在实际开发中可以根据每种方案的优劣性做出权衡,也可以针对性的补充自己的优化方案,尽量避开屏幕适配中的坑。
文中如有错误或者描述不准确的地方,欢迎批评指正。
← 语雀站点搭建指南 项目中遇到的问题总结 →