关于我们
语雀站点搭建指南
语雀站点搭建指南
移动端H5的屏幕适配
移动端H5的屏幕适配
一、相关概念
1.1 屏幕相关
1.2 像素
1.3 Retina屏幕
1.4 视窗(Viewport)
二、适配方案
2.1 Rem方案
2.2 Vw方案
2.3 微信小程序的适配方案
2.4 适配Iphonex
2.5 横屏适配
三、其它常见问题
3.1 1px问题
3.2 图片模糊问题
参考
小结
项目中遇到的问题总结
项目中遇到的问题总结
JavaScript中的LHS查询和RHS查询
Javascript中的Lhs查询和Rhs查询
细说Javascript作用域和闭包
一、Javascript有哪些作用域类型
二、作用域与标识符查询
执行环境与作用域链
标识符查询
三、可以形成独立作用域的结构
函数作用域
块作用域
四、提升
提升的表现
提升的优先级
提升的深层原因
五、跨越词法作用域的两种机制
Eval
With
六、闭包与模块机制
闭包
模块机制

# 移动端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){ } 
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>标签有很多种,而这里要着重说的是viewport的meta标签,它可以告诉浏览器如何规范的渲染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-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
   }
 } 
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的值来修改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`
       }
   }
} 
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不一定能完全整除,因此有一定的像素差。
  • 当容器使用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
} 
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)物理像素,所以视觉效果比较粗。注意这里是视觉效果比较粗,我们谈到视觉效果时这里的衡量单位就不再是像素,而应该是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);
   }
} 
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,从大漠老师文章中得知此方案,需要借助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;
} 
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

# 参考

  • 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的适配有了整体的认识,至少能掌握一种完整的解决方案,在实际开发中可以根据每种方案的优劣性做出权衡,也可以针对性的补充自己的优化方案,尽量避开屏幕适配中的坑。

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

帮助我改善此页面!