移动端适配剖析

原文链接: https://www.cnblogs.com/yalong/p/13301817.html

产生的背景&目标

1.由于手机设备尺寸,屏幕分辨率等不一致,导致页面显示不一致,如下是部分移动端设备的一些参数,更多尺寸看 这里

2.移动端适配的目标是在不同尺寸的手机设备上,页面相对性的达到合理的展示 或者 保持统一效果的等比缩放(看起来差不多) 

概念说明

一.英寸

一般用英寸描述屏幕的物理大小,如电脑显示器的 17、22,手机显示器的 4.8、 5.7等使用的单位都是英寸。

需要注意,上面的尺寸都是屏幕对角线的长度:

 

二.像素

像素即一个小方块,它具有特定的位置和颜色。

图片、电子屏幕(手机、电脑)就是由无数个具有特定颜色和特定位置的小方块拼接而成。

像素可以作为图片或电子屏幕的最小组成单位。

下面我们使用 sketch打开一张图片:

 

将这些图片放大即可看到这些像素点:

 

 

三.屏幕分辨率

屏幕分辨率指一个屏幕具体由多少个像素点组成。

下面是 apple的官网上对手机分辨率的描述:

iPhone XSMax 和 iPhone SE的分辨率分别为 2688x1242和 1136x640。这表示手机分别在垂直和水平上所具有的像素点数. 当然分辨率高不代表屏幕就清晰,屏幕的清晰程度还与尺寸有关

 

四.图像分辨率

我们通常说的 图片分辨率其实是指图片含有的 像素数,比如一张图片的分辨率为 800x400。这表示图片分别在垂直和水平上所具有的像素点数为 800和 400

同一尺寸的图片,分辨率越高,图片越清晰。

 

五.设备独立像素

下面我们来看看 设备独立像素究竟是如何产生的:

智能手机发展非常之快,在几年之前,我们还用着分辨率非常低的手机,比如下面左侧的白色手机,它的分辨率是 320x480,我们可以在上面浏览正常的文字、图片等等。

但是,随着科技的发展,低分辨率的手机已经不能满足我们的需求了。很快,更高分辨率的屏幕诞生了,比如下面的黑色手机,它的分辨率是 640x940,正好是白色手机的两倍。

理论上来讲,在白色手机上相同大小的图片和文字,在黑色手机上会被缩放一倍,因为它的分辨率提高了一倍。这样,岂不是后面出现更高分辨率的手机,页面元素会变得越来越小吗?

然而,事实并不是这样的,我们现在使用的智能手机,不管分辨率多高,他们所展示的界面比例都是基本类似的。

乔布斯在 iPhone4的发布会上首次提出了 RetinaDisplay(视网膜屏幕)的概念,它正是解决了上面的问题,这也使它成为一款跨时代的手机。

在 iPhone4使用的视网膜屏幕中,把 2x2个像素当 1个像素使用,这样让屏幕看起来更精致,但是元素的大小却不会改变。

我们必须用一种单位来同时告诉不同分辨率的手机,它们在界面上显示元素的大小是多少,这个单位就是设备独立像素( DeviceIndependentPixels)简称 DIP或DP。 

上面我们说,列表的宽度为 300个像素,实际上我们可以说:列表的宽度为300个设备独立像素。

打开 chrome的开发者工具,我们可以模拟各个手机型号的显示情况,每种型号上面会显示一个尺寸,

比如 iPhone X显示的尺寸是 375x812,实际 iPhone X的分辨率会比这高很多,这里显示的就是设备独立像素。


 

简单来说,设备独立像素,可以理解为,设备的大小

六.设备像素比

设备像素比 device pixel ratio简称 dpr,即物理像素和设备独立像素的比值。

一般dpr都是大于1的,比如dpr是2 就表示用两个物理像素点去渲染一个设备独立像素点

在 web中,浏览器为我们提供了 window.devicePixelRatio来帮助我们获取 dpr。

在 css中,可以使用媒体查询 min-device-pixel-ratio,区分 dpr:

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

当然,上面的规则也有例外, iPhone6、7、8Plus的实际物理像素是 1080x1920,在开发者工具中我们可以看到:它的设备独立像素是 414x736,设备像素比为 3,

设备独立像素和设备像素比的乘积并不等于 1080x1920,而是等于 1242x2208。

实际上,手机会自动把 1242x2208个像素点塞进 1080*1920个物理像素点来渲染,我们不用关心这个过程,而 1242x2208被称为屏幕的 设计像素。

我们开发过程中也是以这个 设计像素为准。

实际上,从苹果提出视网膜屏幕开始,才出现设备像素比这个概念,因为在这之前,移动设备都是直接使用物理像素来进行展示。

紧接着, Android同样使用了其他的技术方案来实现 DPR大于 1的屏幕,不过原理是类似的。

由于 Android屏幕尺寸非常多、分辨率高低跨度非常大,不像苹果只有它自己的几款固定设备、尺寸。所以,为了保证各种设备的显示效果, Android按照设备的像素密度将设备分成了几个区间。

当然,所有的 Android设备不一定严格按照上面的分辨率,每个类型可能对应几种不同分辨率,所以,每个 Android手机都能根据给定的区间范围,确定自己的 DPR,从而拥有类似的显示。

当然,仅仅是类似,由于各个设备的尺寸、分辨率上的差异,设备独立像素也不会完全相等,所以各种 Android设备仍然不能做到在展示上完全相等。

七.布局视口

 

布局视口( layout viewport):当我们以百分比来指定一个元素的大小时,它的计算值是由这个元素的包含块计算而来的。当这个元素是最顶级的元素时,它就是基于布局视口来计算的。 

所以,布局视口是网页布局的基准窗口,在 PC浏览器上,布局视口就等于当前浏览器的窗口大小(不包括 borders 、 margins、滚动条)。

在移动端,布局视口被赋予一个默认值,大部分为 980px,这保证 PC的网页可以在手机浏览器上呈现,但是非常小,用户可以手动对网页进行放大。

我们可以通过调用 document.documentElement.clientWidth/clientHeight来获取布局视口大小。

八.视觉视口

 

视觉视口( visual viewport):用户通过屏幕真实看到的区域。

视觉视口默认等于当前浏览器的窗口大小(包括滚动条宽度)。

当用户对浏览器进行缩放时,不会改变布局视口的大小,所以页面布局是不变的,但是缩放会改变视觉视口的大小。

例如:用户将浏览器窗口放大了 200%,这时浏览器窗口中的 CSS像素会随着视觉视口的放大而放大,这时一个 CSS像素会跨越更多的物理像素。

所以,布局视口会限制你的 CSS布局而视觉视口决定用户具体能看到什么。

我们可以通过调用 window.innerWidth/innerHeight来获取视觉视口大小。

九.理想视口

布局视口在移动端展示的效果并不是一个理想的效果,所以理想视口( ideal viewport)就诞生了:网站页面在移动端展示的理想大小。 

如上图,我们在描述设备独立像素时曾使用过这张图,在浏览器调试移动端时页面上给定的像素大小就是理想视口大小,它的单位正是设备独立像素。

我们可以通过调用 screen.width/height来获取理想视口大小。

十.Meta viewport

元素表示那些不能由其它 HTML元相关元素之一表示的任何元数据信息,它可以告诉浏览器如何解析页面。

我们可以借助 元素的 viewport来帮助我们设置视口、缩放等,从而让移动端得到更好的展示效果。 更多关于Meta viewport 的看 这里

 

移动端适配方案

一.媒体查询

关于媒体查询详细的介绍看 这里

使用 @media 查询,可以针对不同的媒体类型定义不同的样式。

优点:
  • 前端开发的时候,直接用px,不需要转换单位,比较方便。
  • 跟淘宝flex布局相比 不需要额外使用js去更改html的字体
缺点
  • 缺点: 不连续,由于移动端设备型号太多,不能给每种设备都写适配,无法实现全部设备的统一布局

二.flexible方案

flexible方案是阿里早期开源的一个移动端适配解决方案,引用 flexible后,我们在页面上统一使用rem来布局,把页面分成10份,一份是1rem

它的核心代码非常简单

// set 1rem = viewWidth / 10
function setRemUnit () {
    var rem = docEl.clientWidth / 10
    docEl.style.fontSize = rem + 'px'
}
setRemUnit();  

rem 是相对于 html节点的 font-size来做计算的。

我们通过设置 document.documentElement.style.fontSize就可以统一整个页面的布局标准。

上面的代码中,将 html节点的 font-size设置为页面 clientWidth(布局视口)的 1/10,即 1rem就等于页面布局视口的 1/10,这就意味着我们后面使用的 rem都是按照页面比例来计算的。

这时,我们只需要将 UI出的图转换为 rem即可。

以 iPhone6为例:布局视口为 375px,则 1rem=37.5px,这时 UI给定一个元素的宽为 75px(设备独立像素),我们只需要将它设置为 75/37.5=2rem

优点
  • 跟媒体查询设置根字体大小相比,更连续,能实现更多设备的布局统一
缺点
  • 在奇葩的dpr设备上表现效果不太好,比如一些华为的高端机型 用rem布局会出现错乱。
  • html的font-size设置到12px以下还是会按照12px=1rem来计算,这样所有使用了rem单位的尺寸都是错的
  • 由于 viewport单位得到众多浏览器的兼容,flexible这种方案现在已经被官方弃用:

lib-flexible这个过渡方案已经可以放弃使用,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用viewport来替代此方案。

三.vw vh (viewport)

vhvw方案即将视觉视口宽度 window.innerWidth和视觉视口高度 window.innerHeight 等分为 100 份。

上面的 flexible方案就是模仿这种方案,因为早些时候 vw还没有得到很好的兼容。

  • vw(Viewport's width): 1vw等于视觉视口的 1%
  • vh(Viewport's height) : 1vh 为视觉视口高度的 1%
  • vmin : vw 和 vh 中的较小值
  • vmax : 选取 vw 和 vh 中的较大值
优点
  • 相比rem把页面分成10份,vw把页面分为100份,会更准确
缺点
  • 支持度不太好,兼容性如下, 但是现在都2020年了,兼容性问题已经比较少了,如果业务场景不是很特殊,其实是可以大胆使用的。

四.rem vw 跟px的单位转换

1.最原始的方法 - 手工计算

缺点:太麻烦

2.利用SCSS的函数功能

比如把px转为rem,代码如下:

  //设计稿宽度是 750px, 那么根字体大小设置75px
  html{
    font-size: 75px;
  }

  $baseFontSize:75;//基数
  @function px2rem($px){
    @return $px / $baseFontSize * 1rem;
  }
  //调用
  .box{
    width: px2rem(600);
    height: px2rem(400);;
    background-color: lawngreen;
  }  

缺点:需要引入SCSS函数, 每次都要写 px2rem

3.引入 PostCSS

1.如下是webpck Vue 2.x环境下px转为rem的配置

npm install postcss-loader
var px2rem = require('postcss-px2rem');
module.exports = {
  module: {
    loaders: [
      {
        test: /\.css$/,
        loader: "style-loader!css-loader!postcss-loader"
      }
    ]
  },
  postcss: function() {
    return [px2rem({remUnit: 75})]; //设置基准值,75是以iphone6的标准
  }
}

2.px转为vw

首先安装npm install postcss-px-to-viewport --save-dev 然后配置如下:

//postcss.config.js文件
module.exports = {
  plugins: {
    'postcss-px-to-viewport': {
      unitToConvert: 'px', //需要转换的单位,默认为"px"
      viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度
      viewportHeight: 1334,//视窗的高度,根据375设备的宽度来指定,一般指定667,也可以不配置
      unitPrecision: 13, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
      propList: ['*'], // 能转化为vw的属性列表
      viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
      fontViewportUnit: 'vw', //字体使用的视口单位
      selectorBlackList: ['.ignore-', '.hairlines'], //指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
      minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
      mediaQuery: false, // 允许在媒体查询中转换`px`
      replace: true, //是否直接更换属性值,而不添加备用属性
      exclude: [
        /RightBar/,
        /gotop.vue/,
      ], //忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
      landscape: false, //是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
      landscapeUnit: 'vw', //横屏时使用的单位
      landscapeWidth: 1134 //横屏时使用的视口宽度
    }
  }
}

优点:开发的时候,直接写px, 更方便

缺点:需要相关配置, 不过跟优点相比,这点配置不算啥。

常见问题

1px 问题

网上大部分都是说:

在设备像素比大于1的屏幕上,我们写的1px实际上是被多个物理像素渲染,这就会出现 1px在有些屏幕上看起来很粗的现象。

其实事实就是它并没有变粗,就是css单位中的1px,对于dpr为2的设备,它实际能显示的最小值是0.5px。设计师口中说的1px是针对设备物理像素的,换算成css像素就是0.5px。

一句话总结,background:1px solid black在任何屏幕上都是一样粗的,但是retina屏可以显示比这更细的边框,然后设计师就不乐意了,让你改。

解决1px问题的方法:

1.border-image:还要搞图片,太麻烦不用。

2.伪元素 + 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);
    }
}
3.svg: 缺点是需要编译

border-image可以模拟 1px边框,但是使用的都是位图,还需要外部引入。 借助 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

更准确的说,其实是适配带有刘海的设备,而且这种情况是在混合开发中也就是App上才会遇到,浏览器中是没有这个问题的。

1.针对沉浸式布局(可以理解为全面屏),如下所示, status bar 跟 页面的导航栏重叠了,这种情况只需给导航栏设置一个padding-top即可

 

2.iphoneX的适配问题 如下所示,iphoneX 顶部 底部的白色区域是危险区域,页面需要远离危险区域,

真实的效果如下所示:

解决这种问题的方式有两种:

1,通过媒体查询,判断是iphoneX 给页面添加padding-top 和padding-bottom

2,

<meta name="viewport" content="viewport-fit=cover">
body {
    padding-bottom: constant(safe-area-inset-bottom);
    padding-bottom: env(safe-area-inset-bottom);
}

高清屏幕下图片模糊

我们平时使用的图片大多数都属于位图( png、jpg...),位图由一个个像素点构成的,每个像素都具有特定的位置和颜色值:

理论上,位图的每个像素对应在屏幕上使用一个物理像素来渲染,才能达到最佳的显示效果。

 

而在 dpr>1的屏幕上,位图的一个像素可能由多个物理像素来渲染,然而这些物理像素点并不能被准确的分配上对应位图像素的颜色,只能取近似值,所以相同的图片在 dpr>1的屏幕上就会模糊:

 

为了保证图片质量,我们应该尽可能让一个屏幕像素来渲染一个图片像素,所以,针对不同 DPR的屏幕,我们需要展示不同分辨率的图片。

如:在 dpr=2的屏幕上展示两倍图 (@2x),在 dpr=3的屏幕上展示三倍图 (@3x)。

具体的方法如下:

1.使用 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);

}
  1. image-set (也只适用背景图)
.avatar {
    background-image: -webkit-image-set( "conardLi_1x.png" 1x, "conardLi_2x.png" 2x );
}

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

<img src="conardLi_1x.png"
srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x">

4.使用svg

SVG的全称是可缩放矢量图(ScalableVectorGraphics)。

不同于位图的基于像素,SVG则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。

参考链接:

https://www.jianshu.com/p/d22270046013

https://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html

https://blog.csdn.net/Liuqz2009/article/details/89500080

https://github.com/amfe/article/issues/17

 

posted @ 2020-07-14 21:29  进军的蜗牛  阅读(498)  评论(0编辑  收藏  举报