不要再问我移动适配的问题了

“不要再问我XX的问题”系列:
1、 不要再问我this的指向问题了
2、 不要再问我跨域的问题了

移动端适配的问题,通常来讲咱们都不会去深究,由于这种东西都是配置一次就不再用管的了,接到设计图就按照祖传套路撸就完事了。循序渐进的一定只能成为活动页写手,研究透彻之后,才能成为一名专业的活动页写手嘛。css

纠缠不清的关系

文章开始,咱们须要来捋清楚像素、视口以及缩放之间种种藕断丝连的关系,来抽丝剥茧一波。html

像素

像素咱们写得多了,不就是px嘛,为何要拿出来讲呢?由于像素还不只仅就是px。android

  • 设备像素

设备像素也能够叫物理像素,由设备的屏幕决定,其实就是屏幕中控制显示的最小单位。git

  • 设备独立像素

设备独立像素是一种能够被程序所控制的虚拟像素,在Web开发中对应CSS像素。github

  • DPR

设备像素与设备独立像素之间的关系就是,DPR(设备像素比),设备像素比 = 设备像素 / 设备独立像素。这条公式成立的前提是,缩放比为1,缘由下面讲到缩放的时候就会知道。根据这种关系,若是设备像素大于设备独立像素(DPR大于1的设备,咱们常说的高清屏或者Retina屏),就会出现一个设备独立像素对应多个设备像素的状况:
图片描述segmentfault

视口

遥想从前智能手机刚出的时候,不多网站去特地适配移动端,然而用户是能够直接从手机去访问PC端网站的,因此怎样显示好一个网站,不管这个网站是一个PC网站仍是移动端网站,就是亟需解决的问题。因此移动端三个视口布局视口、视觉视口、理想视口横空出世,成为各类移动适配方案的基础。跨域

  • 布局视口

布局视口是在html元素之上的容器,咱们的页面就“装”在布局视口中。
想一想咱们常写的width:100%,这个100%是基于什么计算出来的呢?去翻资料会看到:若是某些属性被赋予一个百分值的话,它的计算值是由这个元素的包含块计算而来的。那html元素的包含块是什么呢?没错,就是咱们的布局视口,它是全部CSS百分比推算的根源,若是说CSS是一支画笔,那么布局视口就是那张画布吧。这张画布有一个默认尺寸(若是没有手动去设置meta viewport),通常在768px ~ 1024px间,能够经过document.documentElement.clientWidth获取。这样一来,网页的布局就再也不受限于设备的尺寸,即便是小屏幕的移动端设备中也能容得下PC网站。
布局视口浏览器

  • 视觉视口

视觉视口是指用户经过设备屏幕看到的区域,能够经过缩放来改变视觉视口的大小,并经过window.innerWidth获取。
这里有必要讲一下缩放,缩放改变的是CSS像素的大小,放大时CSS像素增大,则一个CSS像素能够跨越更多的设备像素,视觉视口会变小。什么?放大反而视觉视口变小?没错,这是由于视觉视口也是经过CSS像素度量,而放大就是使CSS像素放大,假设屏幕上原本须要200个CSS像素才能占满屏幕,因为放大,如今只须要100个CSS像素就能占满,因此视觉视口的宽就变成100px。
虽然缩放改变了CSS像素的大小,但移动端的缩放是不会改变布局视口的,因此缩放并不会影响布局,不过在PC端是会影响布局的。最直观的感觉是,咱们平时在移动端双指缩放网页,整个网页的布局是没有变化的,能够经过拖动来看到不一样区域的东西,可是在PC端进行缩放,好比阅读时想文字大一些而对网页进行放大操做,这时字是放大了,但整个页面的布局会有所改变。那么既然与布局视口无关那还跟谁有关系呢?答案就是下面准备要讲的理想视口,它们之间的计算方式是:缩放系数 = 理想视口宽度 / 视觉视口宽度
视觉视口sass

  • 理想视口

理想视口是指网站在移动设备中的理想大小,这个大小就是设备的屏幕大小。
为何须要理想视口呢?首先,先来看看如今的状况是怎么的不理想。咱们在浏览一个没通过移动适配的网站时,因为布局视口在768px ~ 1024px之间,整个网站就“画”在一个这么大的“画布”上,但因为手机屏幕比“画布”小,因此须要通过缩小才能塞进手机屏幕,结果咱们浏览网站的时候虽然看得见全貌,但里面的东西都变得很小,须要放大一下才能看得清,就是这么不理想。若是不须要放大就能够看得清那就很理想了嘛。回想一下上面不理想的解决方案,就是将一个大画布通过缩小装进小屏幕里,假设如今画布跟屏幕同样大,就在这个画布上做画,岂不是很合适?
因此总结起来,理想视口说白了就是理想的布局视口,经过<meta name="viewport" content="width=device-width, initial-scale=1">来设置。app

将它们连在一块儿

关联

认识Meta viewport

<meta> 元素可提供有关页面的元信息,不会显示在页面上,能够用来告诉浏览器怎样解析页面。<meta>能够设置的东西不少,但这里只讲vieport,它是全部移动适配方案的基础。
首先meta viewport的设置格式是<meta name="viewport" content="name=value,name=value",其中name的值可设为:

  1. width:将布局视口设置为固定的值,好比375px或者device-width(设备宽度)
  2. initial-scale:设置页面的初始缩放
  3. minimum-scale:设置最小的缩小程度
  4. maximum-scale:设置最大的放大程度
  5. user-scalable:设置为no时禁用缩放

虽然只有五个值,但仍有一些值得注意的点:

设置initial-scale的影响

根据公式缩放系数 = 理想视口宽度 / 视觉视口宽度 ,若是设置了initial-scale好比为0.5,那么以iPhone6为例,iPhone6的设备宽度是375px,即理想视口宽度也为375px,因此视觉视口宽度 = 375px(理想视口宽度)/ 0.5(缩放系数)。很明显设置了initial-scale就至关于初始化了视觉视口,并且会将布局视口初始化为这个视觉视口的值

width和initial-scale共存

上面说到设置了initial-scale至关于初始化了视觉视口和布局视口,但width用于指定布局视口的大小,那么一块儿设置的话听谁的呢?
仍是以iPhone6为例,它的尺寸是667(h) * 375(w),若是设置<meta name="viewport" content="width=400, initial-scale=1">,执行一下console.log(`布局视口: ${document.documentElement.clientWidth}; 视觉视口: ${window.innerWidth}`)会获得“布局视口: 400; 视觉视口: 400”。
这时候旋转一下设备,这时尺寸变成了667(w) * 375(h),再执行一下console.log(`布局视口: ${document.documentElement.clientWidth}; 视觉视口: ${window.innerWidth}`)会获得“布局视口: 667; 视觉视口: 667”。
结论是:width与initial-scale都会初始化布局视口,但浏览器会取其最大值。

设置理想视口

这时候再看回<meta name="viewport" content="width=device-width, initial-scale=1">,明明width=device-width和initial-scale=1都是去初始化布局视口成理想的布局视口,只写其中一个不就完了嘛,为何要两个都一块儿写呢?由于有的浏览器只设置其中一个,不能保证理想视口的尺寸能随着屏幕的旋转而正确改变,因此两个一块儿写只是为了解决兼容性问题。

舒服地还原移动端设计图

上面说了不少理论知识,其实就是为了能有一套方案舒服地还原移动端设计图,作出一个专为移动端访问的页面。

经典的问题

  • 图片

这里的图片问题是指高清/Retina屏下图片会显示得比较模糊,这是由于咱们平时使用的图片大多数是png、jpg这样格式的图片,它们称做是位图图像(bitmap),是由一个个像素点构成,缩放会失真。上面讲像素的时候说过,这种高清/Retina屏DPR大于一,则一像素横跨了多个设备像素,而位图图像须要一个像素点对应一个设备像素才清晰。因此假设一张100 x 100的图片放在普通屏上看是清晰的,放到高清/Retina屏上就会显得比较模糊,那是由于原本100 x 100的图片在普通屏上图片像素与设备像素一一对应,而到了高清/Retina屏上一个图片像素却要对应多个设备像素,这样一来看起来图片就比较模糊。
图片问题
如图所示,若是一个图片像素要对应多个设备像素的话,那这些设备像素只能显示成跟这个图片像素差很少的颜色,致使看起来会模糊。
既然知道了问题产生的缘由,那解决方法也很简单,位图图像须要一个像素点对应一个设备像素才清晰嘛,那就原本是100 x 100的图片在DPR为1的屏幕上显示清晰,在DPR为2的屏幕上显示模糊,那就在DPR为2的屏幕上放200 x 200的图好了,这样就一一对应了。

  • 1px边框

1px边框
“你看看设计图这根线是很细的,为何你实现出来那么粗,看起来很劣质的感受。”
没道理呀,设计图量的是1px,css写的也是1px,怎么会粗了呢?通常设计师出图的时候,都会按照一个尺寸做为标准来出图,好比按照iPhone6的尺寸出图,就是一张750px宽的设计图,这个750px其实就是iPhone6的设备像素,在测量设计图时量到的1px实际上是1设备像素,而当咱们设置<meta name="viewport" content="width=device-width, initial-scale=1">时,布局视口等于理想视口等于375px,而且因为iPhone6的DPR为2,写css时的1px对应的是2设备像素,因此看起来会粗一点。
那么只要写0.5px就是对应1设备像素了嘛。是的,道理是这么说,可是不少浏览器并不支持0.5px的写法,致使显示不出来,但没关系,网上不少方法解决这个问题的方法就不细说了,这里只是讲清楚1px边框问题产生的缘由。

还原设计图

由于PC端屏幕通常都会比设计图尺寸要大,因此只须要居中固定一个内容区用于显示设计图的内容,其他多出的地方留白便可。而移动端屏幕有大有小,设计图通常会以一款机型为标准来出图,好比说iPhone6的尺寸,若是不经处理直接量设计图就开干会出现什么问题呢?
对比
(从左到右为iPhone四、iPhone六、iPhone plus)
能够看到以iPhone6为标准出的设计图测量出来350px x 350px的元素在iPhone6上写width: 350px;height: 350px;是刚恰好的,左右的间隙各有10px,但小一点的屏幕iPhone4横向滚动条都出来了,而plus左右间隙明显比10px大不少,这样一来不一样尺寸的屏幕出来的效果跟设计图的效果就会有不一样程度的出入,这并非咱们想要的,咱们想要的是不一样尺寸的屏幕显示的效果与设计图比例是一致的。
既然想要的是不一样屏幕尺寸显示的比例与设计图一致,那么显然适配方案就是等比缩放
(如下代码都是为了讲述原理,没有过多的细节考虑与测试,不能用于生产环境)

  • viewport方案

说到缩放,首先想到的固然是initial-scale。回想一下initial-scale的做用:设置了initial-scale就至关于初始化了视觉视口,并且会将布局视口初始化为这个视觉视口的值。那么咱们是否是能够以设计图为基准等比缩放布局视口从而适配呢?

<script>
    const scale = window.screen.width / 750
    document.write(`<meta name="viewport" content="initial-scale=${scale}">`)
</script>

这种方式进行适配优势是简单粗暴,缺点是太简单粗暴了,由于viewport的设置是影响全局的,这样一来虽然能够直接将设计图量得的尺寸写到css上,但若是有一些须要地方不须要等比缩放而须要设置固定尺寸,好比要求在不一样尺寸屏幕上显示固定大小的文字,或者你引进了一个库,里面的有样式你也不知道人家是按照怎样的适配方案进行适配的,那么到了你的项目里因为全局的viewport缩放,可能会影响到这个库的显示效果。

  • rem方案

不一样于px是固定尺寸单位,rem是相对单位,相对于html标签字体大小的单位。好比html标签的font-size为100px,那么1rem就等于100px。借助rem这个相对单位咱们一样能够达到等比缩放的效果。

  • 这个方案不须要对viewport进行缩放,因此首先按照惯例咱们让布局视口等于理想视口:<meta name="viewport" content="width=device-width, initial-scale=1">
  • 仍是以iPhone6的设备像素为标准的设计图,宽是750px,假设以设计图为标准的html标签的font-size为100px,因此1rem = 100px,那么这个设计图总宽就有7.5rem
  • 以总宽是7.5rem的设计图为标准,则不一样屏幕尺寸的总宽应该也是7.5rem,因为上面设置了布局视口等于理想视口,因此以iPhone6为例,iPhone6的布局视口等于理想视口,则它的布局视口为375px(也就是总宽7.5rem),如今只须要解决在布局视口为375px的状况下,html的font-size须要设置多少。很简单,html font-size * 7.5 = 375,那么font-size为50px。
  • 拓展到其余屏幕document.documentElement.style.fontSize = `${document.documentElement.clientWidth / 7.5}px`
  • 如今咱们只须要测量设计图,好比设计图有一个300px的元素,那咱们写css的时候就写成3rem(因为以1rem = 100px为基准,因此这里300px / 100便可)

使用这个方案,咱们只对须要等比缩放的元素使用rem,而要求固定尺寸的地方使用px便可,这样一来相对于viewport方案来讲就比较灵活,能够按需使用而不是一刀切。不过这种方案写css的时候可能会没那么直观,成本可能会高一点点,可是借助构建工具或者less/sass能够解决,毕竟如今应该不多项目不使用这些工具的了吧。

  • 增强版rem方案

这里所说的增强版rem方案其实就是手淘的Flexible方案(也相似移动端高清、多屏适配方案),究竟增强了什么呢?那就是,经过设置viewport进而全局解决1px边框问题。
既然要经过设置viewport来解决1px边框问题,那设置这个viewport的方式确定内有乾坤:

if (!dpr && !scale) {
    var isAndroid = win.navigator.appVersion.match(/android/gi);
    var isIPhone = win.navigator.appVersion.match(/iphone/gi);
    var devicePixelRatio = win.devicePixelRatio;
    if (isIPhone) {
        if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {                
            dpr = 3;
        } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
            dpr = 2;
        } else {
            dpr = 1;
        }
    } else {
        // 其余设备下,仍旧使用1倍的方案
        dpr = 1;
    }
    scale = 1 / dpr;
}

得出的scale用于设置viewport的缩放document.write(`<meta name="viewport" content="initial-scale=${scale}">`),这样一来,对于Retina屏将viewport缩放为1 / dpr最终产生的效果是,1px css像素严格等于1px 设备像素,由此解决了1px边框问题。那为何只对iPhone进行缩放呢?请看大漠老师的文章再谈Retina下1px的解决方案
其余与rem相关的配置与上面的rem方案相似,这里就再也不展开说了。
这个增强版rem方案最大的优点是解决了1px边框问题,但由此也进行了viewport的缩放,仍然会面临着上面说的viewport方案涉及到的一些影响,为此该方案会经过给html设置data-dpr

document.documentElement.setAttribute('data-dpr', dpr)

从而写css的时候能够针对不一样的dpr固定设置尺寸:

.test {
    width: 1rem; 
    height: 2rem;
    font-size: 12px; 
}
[data-dpr="2"] .test {
    font-size: 13px;
}
[data-dpr="3"] .test {
    font-size: 14px;
}
  • vw方案

vw也是一个相对单位,它相对的是布局视口,1vw就是1%的布局视口宽度。其实rem方案就是在模拟vw,来看看使用vw怎么作。

  1. 仍是熟悉的iPhone6标准设计图,宽750px。那么1vw = 1%视口宽度的话,按设计图来讲就是100vw = 750px,则1vw = 7.5px。
  2. 设计图量得一个元素是100px,css须要写成 Xvw * 7.5 = 100,因此X就等于13.3vw。
  3. 计算的话仍是交给构建工具便可,详细请看再聊移动端页面的适配

rem方案有的优点vw也有,并且也不会像rem那么绕,但就是兼容性不够rem好,长远来看vw最后会接棒rem做为移动适配的主力,由于它生来就干这个事情呢。

终于结束了

没有银弹。全局viewport缩放方案很粗暴?但对于要求不高也不须要兼顾固定尺寸的页面,上来就全局缩放,拿起设计稿就能够写代码了。要求高又想灵活,还会怕构建的那一点点麻烦吗?rem方案走起。兼容性不须要考虑,那vw方案直白又优雅不试试看吗?方案没有优劣之分只有合适与否。最后,若是有说得不对的地方,还望指正。

相关文章
相关标签/搜索