Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端移动端适配总结 #1

Open
xunan007 opened this issue Feb 23, 2018 · 0 comments
Open

前端移动端适配总结 #1

xunan007 opened this issue Feb 23, 2018 · 0 comments

Comments

@xunan007
Copy link
Owner

xunan007 commented Feb 23, 2018

meta标签到底做了什么事情

做过移动端适配的小伙伴一定有遇到过这行代码:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

但是,很多小伙伴只是感性的认识:噢,我加了这行代码,然后页面的宽度就会跟我的设备宽度一致。然而,这种理解是很片面的。那么,这句话的本质到底是什么呢?

不急,我们先往下面看,这里先留个悬念。

几个专有名词和单位

这里,我们先来辨析一下在适配的时候经常会遇到的一些名词、数值单位。

首先,先来看一下物理像素

以iphone6为例,可知道:

分辨率:1334pt x 750pt
指的是屏幕上垂直有1334个物理像素,水平有750个物理像素。

屏幕尺寸:4.7in
注意英寸是长度单位,不是面积单位。4.7英寸指的是屏幕对角线的长度,1英寸等于2.54cm。

屏幕像素密度:326ppi
指的是每英寸屏幕所拥有的像素数,在显示器中,dpi=ppi。dpi强调的是每英寸多少点。同时,屏幕像素密度=分辨率/屏幕尺寸

接着,我们来看一下其他的单位。

设备独立像素:设备独立像素,不同于设备像素(物理像素),它是虚拟化的。比如说css像素,我们常说的10px其实指的就是它。需要注意的是,物理像素开发者是无法获取的,它是自然存在的一种东西,该是多少就是多少。

设备像素比:缩写简称dpr,也就是我们经常在谷歌控制台移动端调试顶端会看到的一个值。设备像素比 = 设备像素 / css像素(垂直方向或水平方向)。可以通过JS来获取:window.devicePixelRatio

PC和移动端不同的视口

注:以下涉及的像素均为CSS像素。并且默认不考虑缩放。

布局视口

写过css的小伙伴应该知道,我们在htmlbody设置width:100%;height:100%;的时候,它并不是无效的。我们都知道100%这种百分数应该是继承父元素而来的。那在这里是继承哪里的呢?

PC浏览器中,有一个用来约束CSS布局视口的东西,又叫做初始包含块。这也就是所有宽高继承的由来。除去marginpadding,布局视口和浏览器可视窗口宽度是一致的,同时也和浏览器本身的宽度一致。

但是在移动端,就大不一样了。

以下的例子是在不加meta标签的前提下进行演示的。

假如我们现在做一个二八分的左右布局,那么如果在PC端上面的话,显示的效果非常完美,这没什么好说的。

那如果是在手机端呢,这里以iphone6为例子来讲解:

图例如下:

image

代码如下:

* {
    margin: 0;
    padding: 0;
}

html,
body {
    height: 100%;
    width: 100%;
}

.left {
    float: left;
    width: 20%;
    height: 100%;
    background: red;
}

.right {
    float: right;
    width: 80%;
    height: 100%;
    background: green;
}
----
<body>
    <div class="left"></div>
    <div class="right"></div>
</body>

这里我们会看到,为什么body的宽度是980px,而浏览器的宽度只有375px,那么这个980px到底是从哪里来的呢?

其实,这里的980px就是移动端所谓的布局视口了。

在移动端,默认的情况下,布局视口的宽度是要远远大于浏览器的宽度的。这两个视口不同于PC端,是相互独立存在的。为什么呢?试想一下,如果一个网页不对移动端进行适配,用户进行阅读的时候,如果默认情况下布局视口的宽度等于浏览器宽度,那是不是展示起来更加的不友好。也就是说,如果一个div的宽度为20%,那么它在布局视口宽度为980px的时候,展示给用户的像素还有196px,而如果宽度只有375px的情况下,宽度只有75px,展示的大小相差特别大。

所以,浏览器厂商为了让用户在小屏幕下网页也能够显示地很好,所以把布局视口宽度设置地很大,一般在768px ~ 1024px之间,最常见的宽度是980px。这个宽度可以通过document.documentElement.clientWidth得到。

视觉视口

对于视觉视口来说,这个东西是呈现给用户的,它是用户看到网页区域内CSS像素的数量。由于用户可以自行进行缩放控制,所以这个视口并不是开发者需要重点关注的。

值得注意的是,在移动端缩放不会改变布局视口的宽度,当缩小的时候,屏幕覆盖的css像素变多,视觉视口变大,反之亦然。

而在PC端,缩放对应布局宽度和视觉窗口宽度都是联动的。而浏览器宽度本身是固定的,无论怎么缩放都不受影响。

如果对上面的宽度还是很乱,那么这里有一个表格可以帮助你理清思路。

以下表格横向都以浏览器窗口的宽度作为基准:

对于PC端来说:
image

对于移动端来说:
image

理想视口

以上,布局视口很明显对用户十分的不友好,完全忽略了手机本来的尺寸。

所以苹果引入了理想视口的概念,它是对设备来说最理想的布局视口尺寸。理想视口中的网页用户最理想的宽度,用户进入页面的时候不需要缩放。

那么很明显,所谓的理想宽度就是浏览器(屏幕)的宽度了。

所以就有了下面的这段代码:

<meta name="viewport" content="width=device-width">

然而,这段代码其实也并不完美,在IE浏览器中,由于横屏竖屏的切换会对其造成影响,为了解决这个兼容性的问题,最后再加上一句,就有了现在的:

<meta name="viewport" content="width=device-width,initial-scale=1">

width=device-width 这句代码可以把布局视口设置成为浏览器(屏幕)的宽度。

initial-scale=1 的意思是初始缩放的比例是1,使用它的时候,同时也会将布局视口的尺寸设置为缩放后的尺寸。而缩放的尺寸就是基于屏幕的宽度来的,也就起到了和width=device-width同样的效果。

另外,值得一提的是,我们在进行媒体查询的时候,查询的宽度值其实也是布局视口的宽度值。

Retina屏幕&普通屏幕,模糊的由来

dpr的具体表现

有时候我们会发现,当我们在适某一机型的时候,显示上没什么问题。但是一旦我换到另外一部手机,发现出现了模糊的情况,尤其以图片更为显著。

其实这个问题,就是涉及到了上面讲到的一个属性:设备像素比,即我们经常说的dpr。下面先来看dpr的表现:

假设现在有一台iphone6,那么它的设备独立像素是375x667,dpr为2,尺寸是4.7in,那么物理像素就是750x1334。
同样的我们也有一台不知名的设备,它的设备独立像素刚好也是375x667,尺寸也是4.7in,但是dpr为1,此时的物理像素就是375x667。

于是,它们的屏幕表现如下:

image

在不同的屏幕上,无论是普通屏幕还是retina屏幕,css像素所呈现的大小是一致的。(如果不理解这句话,可以写一个2px的正方形使用谷歌控制台移动设备调试,在不同的设备之间来回切换,你会发现大小其实是一样的。一开始我总以为这个css像素的实际宽高因为受到dpr的影响而在不同设备上的长宽是不一致的。)

不同的是,1个css像素对应(覆盖)的物理像素个数。

所以,如果我们想要在这两个屏幕显示这么一个css样式:

width: 2px;
heigth: 2px;

在普通屏幕下,也就是dpr为1的屏幕中,1个css像素对应(覆盖)的是一个物理像素。在retina屏幕下,1个css像素对应(覆盖)的是4个物理像素。换句话说,就是dpr为2的设备。看下面这张图:

image

浅显的理解就是可以看作是2cmx2cm的正方形被切割成四块,然后遇到dpr为2的时候,被切割的四块又被分别切割成四块,但是总面积不变。

模糊的产生

知道了1个css像素覆盖的物理像素可能不同,就好理解为什么会出现模糊的情况了。

这里又讲到一个名词:位图像素

位图像素是栅格图像(如:png,jpg,gif等)最小的数据单元。每一个位图像素都包含着一些自身的显示信息。(如:显示位置,颜色值,透明度等)

理论上来说,1个位图像素对应1个物理像素,图片才能达到完美清晰的展示

但是上面说过,在retina屏幕上,会出现1个位图像素对应多个物理像素。

还是以iphone6为例,1个位图像素对应4个物理像素。由于单个位图像素已经是最小的数据单位了,它不能再被进行切割。于是为了能够显示出来,就只能就近取色,从而导致所谓的图片模糊问题。如下:

image

如何解决

很明显,由于位图像素不够分而产生模糊的情况,解决的办法十分简单,就是使用跟dpr同个倍数大小的图片。比如iphone6,一个200x300的img标签,原图就要提供400x600的大小。

那么当加载到img标签中,浏览器会自动对每1px的css像素减半,可以理解为此时还是维持着1:1的css像素:物理像素,不产生模糊。

这个做法其实就是手淘团队在做retina适配的一个重要的原理之一,后面会讲到,这里先放着不说。

其他

反向思考一下,如果普通屏幕,也就是dpr为1的屏幕,也使用了两倍的图片,会发生什么样的情况呢?

很明显,在普通屏幕下,200×300的img标签,所对应的物理像素个数就是200×300个,而两倍图片的位图像素个数则是200x300x4,于是就出现一个物理像素点对应4个位图像素点,所以它的取色也只能通过一定的算法进行缩减,显示结果就是一张只有原图像素总数四分之一,肉眼看上去虽然图片不会模糊,但是会觉得有点色差。(其实就是模糊的逆向过程)

用图片来表示就是:

image

这里摘取了网上一篇博文的demo来阐述上面所说的问题。

image

以上是一张100x100的图片,分别放在了100x100,50x50,25x25的容器中,在retina屏幕下面的显示效果。

通过取色器放大镜可以看出边界像素点的差别:

在图一中,边界像素点就近取色,色值介于红白之间,偏淡,图片看上去会模糊(可以理解为图片拉伸)。

在图二中,图片正常,很清晰。

在图三中,边界像素点就近取色,色值介于红白之间,偏浓,图片看上去有色差。

手淘团队flexible.js布局

现今,适配手机端的传统rem布局已经逐步被手淘团队的一套flexible布局代替。

具体的实现方式以及细节这里也不铺开来说,具体参考w3cplus的一篇文章,很容易读懂和理解。

这里我更想分析一下flexible.js做法的意义和原因。

读过文章之后,相信大家应该对整个开发适配的流程比较熟悉了。

假设现在要适配一个iphone6的设备。上面已经说过了iphone6的各个参数,这里不再赘述,需要的自行上移查看。

于是:

  1. 设计师给了一个750px宽度的设计稿(注意这里是750px而不是375px)
  2. 前端工程师用750px的这个比例开始还原
  3. 把宽高是px的转换成rem
  4. 字体使用px而不使用rem
  5. flexible.js会自动判断dpr进行整个布局视口的放缩

rem布局和字体的处理

从flexible.js中可见,在宽高中使用的是rem,这是为了保证在不同宽度尺寸的设备中能够保证布局的等比例缩放。

而为什么字体不使用rem而是采用px呢?

首先,用过rem单位的小伙伴都会发现,使用rem后由于不同的尺寸,换算之后出现各种奇奇怪怪的数值,最为明显的就是更多的小数位,比如13.755px之类的数值。在浏览器中,各浏览器中对小数点的计算存在偏差,而且有些带小数的font-size值在特定的浏览器显示并不够清晰。

其次,我们希望在小屏幕下面显示跟大屏幕同等量的字体。并且如果使用rem的话,那么由于等比例的存在,在小屏幕下就会存在小屏幕字体更小的情况,不利于我们更好的去阅读,违背了适配的初衷。所以,对于字体的适配,更好的做法就是使用px和媒体查询来进行适配。

所以,也就不难解释为什么要对font-size进行放大的处理了,如下的sass代码:

@mixin font-dpr($font-size) {
    font-size: $font-size;
    [data-dpr="2"] & {
        font-size: $font-size * 2;
    }
    [data-dpr="3"] & {
        font-size: $font-size * 3;
    }
}

由于retina屏幕下dpr的不同,我们又想显示的字体一样大,于是就给字体再增大dpr的倍数,这样当缩小dpr倍的时候,那么字体也就和设计稿所示的大小一样大了,在不同的手机中显示的大小也是一致的。

Retina屏幕下的处理与安卓手机的适配

从flexible.js的代码中可以知道,flexible布局仅仅只是针对iPhone进行适配,而默认所有的安卓设备都强制性设置dpr为1。

于是,因为这个缘故,很多小伙伴可能就会产生这样的问题:为什么安卓不用retina屏幕,安卓下面是不是就不会有模糊的问题?

其实不然,模糊的本质是因为dpr,而安卓手机不同的设备的dpr也是不尽相同的。也就是说,安卓手机下也存在模糊的情况。只不过它的屏幕不叫retina屏幕,没有这个叫法,所以很多小伙伴都误认为安卓手机没有这个毛病。

那么问题又来了?既然也有模糊的毛病,那么为什么安卓手机不进行适配呢?

问题就在这里了,有兴趣的小伙伴可以去看一下大中华的安卓手机,dpr参数五花八门,从1到4,连1.75、2.75这种奇葩的数字也有,所以个人觉得权衡之下,直接简单“粗暴”把安卓手机全部设置为1,是效率和收益更高的做法。

当然,也有人进行了flexible.js的改进,就是对dpr比较正常的安卓手机进行适配,也就是说只适配dpr为整数的安卓设备。对于那些奇葩的dpr为1.75的设备直接忽略。实现这个并不难,有兴趣的小伙伴们可以试下。

响应式与自适应的选择

最后,对于响应式和自适应的区别,网上有各种各样的解释。

个人认为,其实没必要把它讲得那么复杂,知乎上有个小伙伴讲我觉得就很白话文:

响应式针对的是不同分辨率设备而进行的适配式设计,以利用@media规则为主要手段,而自适应则忽略@media以比例布局为主,目的是适应不同的浏览器窗口大小。

于是我们会发现,现今大型网站,例如说淘宝网,已经没有做响应式了。什么意思呢?

我们会发现,淘宝网手机端和网页端使用的是两个域名,也就是说,不同的客户端已经不再共用一套dom结构了。而是区分开来做自适应。然后每次用户访问的时候它就根据客户端的类型重定向。

为什么呢?

试想一下淘宝这种大型网站,一个分页下的商品条目特别多,并且每个商品条目的dom结构又十分复杂,而且pc端往往显示的信息是要比手机端更多的。如果不分开做两套,而是直接用响应式的话,那么pc端上显示的很多dom就要在手机端上隐藏,结果这些dom都没有被用到,但是却加载了。在这个流量和速度至上的时代,代码冗余先不说,多加载的这些无用的代码而消耗的流量,从某种意义上来说就已经损失了很多的效益。

最后

考虑到兼容性的问题,原先我们在文章头部说到的那段代码:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

Chrome32+版本开始是会默认禁用用户缩放的,但是考虑到兼容大部分设备,还是要加上其他设置,让meta标签能够有更好的容错性。也就是下面这段代码:

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

需要注意的是,在ios10+以上,尽管开发者设置了user-scalable=noSafari还是允许用户通过手势来缩放。(安卓手机各大厂商的内置浏览器也逐渐开放用户缩放,即使使用meta标签进行设置)

解决的方法也很简单,只需要检测touch相关事件来阻止事件的触发即可。

window.onload = function() {
    // 同时按下两个手指
    document.addEventListener('touchstart', function(event) {
        if(event.touches.length > 1) {
            event.preventDefault()
        }
    })
    var lastTouchEnd = 0;
    // 特别注意300ms时差的设置
    document.addEventListener('touchend', function(event) {
        var now = (new Date()).getTime();
        if(now-lastTouchEnd <= 300) {
            event.preventDefault();
        }
        lastTouchEnd = now;
    })
}

以上,就是本文的全部啦。

文章有借鉴,借鉴的链接都会在这里放出来。

前辈们的经验和知识很宝贵,我们需要做的,是站在巨人的肩膀上,去提炼这些东西,有自己更好的理解、思考和开拓新知识面。

相关链接:

移动端适配方案(主要讲解的是移动端视口方面的知识):
https://segmentfault.com/a/1190000004336869
https://segmentfault.com/a/1190000004358316

Retina屏幕下模糊的由来:
http://mobile.51cto.com/web-484304.htm

手淘flexible.js布局:
http://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant