移动端适配剖析
原文链接: 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)
vh
、vw
方案即将视觉视口宽度 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); }
- 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