移动端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 6PPI为 326,它每英寸约含有326个物理像素点。 

1.2 像素

设备像素(Device Pixel)

设备像素也称物理像素,是屏幕硬件设备能控制显示的最小单位,操作系统可以为每个物理像素自己的颜色和亮度,每个物理像素的大小是屏幕固有的属性,屏幕出厂以后就不会改变了。

设备独立像素(Device Independent Pixel)

设备独立像素是操作系统定义的一种像素单位,也称逻辑像素。操作系统在屏幕屏幕硬件支持基础上,通过设备独立像素调节最佳的显示比例。

在PC端,几乎所有的图形化操作系统都支持手动调整逻辑像素

但在移动端,我们很少关注到相关设置项,其实是因为只是使用了AndroidiOS默认设置。

设备像素比(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){ } 
1
  • 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>标签有很多种,而这里要着重说的是viewportmeta标签,它可以告诉浏览器如何规范的渲染Web页面。在移动端为了得到更好的展示效果,通常需要设置meta标签如下:

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

content中参数的含义如下:

Value可能值描述
width正整数或device-width以pixels(像素)为单位, 定义布局视窗的宽度。
height正整数或device-width以pixels(像素)为单位, 定义布局视窗的高度。
initial-scale0.0 - 10.0定义页面初始缩放比率。
minimum-scale0.0 - 10.0定义缩放的最小值;必须小于或等于maximum-scale的值。
maximum-scale0.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-scale800px,布局视窗取两者最大值即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
   }
 } 
1
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()
}; 
1
2
3
4
5
6
7
8
9
10
11

该方案是阿里手淘早期使用,并封装开源了lib-flexible库,它的主要思想有三点:

  • 根据dpr的值来修改viewport实现1px的线
  • 根据dpr的值来修改htmlfont-size,从而使用rem实现等比缩放
  • 使用Hack手段用rem模拟vw特性
    存在的问题:
  • 需要额外内联引入一个lib-flexible库,保证在所有资源加载之前执行这个JS,使用iframe引用就会出现问题。
  • htmlfont-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,px2vwpx-to-vw这样的插件还是能找到的,效果如下:

    另外我们还可以使用postCSSpostCSS-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`
       }
   }
} 
1
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> 
1
.test {
   width: 75px;
   border-bottom-width: 4px;
   line-height: 20px;
}
.ignore {
   margin: 10px;
   background-color: red;
} 
1
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;
} 
1
2
3
4
5
6
7
8
9

当然,没有一种方案是十全十美的,vw同样有一定的缺陷:

  • px转换成vw不一定能完全整除,因此有一定的像素差。
  • 当容器使用vwmargin采用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-fitmeta标签作为适配方案, 它用于限制网页如何在安全区域内进行展示。

viewport-fit 的设置如下:

  • contain布局视窗视觉视窗被设置为最大的矩形,viewport完全包含网页内容,在viewport之外的UA绘制的是未定义的,它可能是画布的背景色,或者UA认为合适的其他东西。
  • cover布局视窗视觉视窗被设置为设备物理屏幕的限定矩形,如上图2所示,网页内容完全覆盖可视窗口。
  • auto: 默认设置auto,和contain效果一致。

env、constant

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

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

  • safe-area-inset-left:安全区域距离左边边界距离
  • safe-area-inset-right:安全区域距离右边边界距离
  • safe-area-inset-top:安全区域距离顶部边界距离
  • safe-area-inset-bottom:安全区域距离底部边界距离
    在设置viewport-fit=cove的基础上,constantiOS < 11.2的版本中生效,enviOS >= 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
} 
1
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('横屏');
   }  
}); 
1
2
3
4
5
6
7
8
9
10
  • CSS也可以检测到屏幕旋转
@media screen and (orientation: portrait) {
   /*竖屏...*/
 }
 @media screen and (orientation: landscape) {
   /*横屏...*/
 } 
1
2
3
4
5
6

三、其它常见问题

3.1 1px问题

产生原因:目前移动设备普遍的设备像素比为2或3,1pxCSS像素实际渲染为2(3)物理像素,所以视觉效果比较粗。注意这里是视觉效果比较粗,我们谈到视觉效果时这里的衡量单位就不再是像素,而应该是cmmm甚至μ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);
   }
} 
1
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;
   }
} 
1
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;
   }
} 
1
2
3
4
5
6
7
8
9
  • 使用SVG作为border-image或者background-image,从大漠老师文章中得知此方案,需要借助PostCSSpostcss-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;
} 
1
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; } 
1
  • 注意: 直接将宽设为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);
   }
} 
1
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 );
} 
1
2
3
  • img标签引入的图片,可以通过设置srcset属性,浏览器会自动根据像素密度匹配最佳显示图片,示例如下:
<img src="conardLi_1x.png" srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x"> 
1
  • 使用JS根据获取的设备像素比,批量全局替换掉所有图片src,
const dpr = window.devicePixelRatio;
const images =  document.querySelectorAll('img');
images.forEach((img)=>{
 img.src.replace(".", `@${dpr}x.`);
}) 
1
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]"> 
1
2
3
4
5
6
7

参考

小结

通过本文的介绍,你应该对移动端H5的适配有了整体的认识,至少能掌握一种完整的解决方案,在实际开发中可以根据每种方案的优劣性做出权衡,也可以针对性的补充自己的优化方案,尽量避开屏幕适配中的坑。

文中如有错误或者描述不准确的地方,欢迎批评指正。