移动端布局适配的方案
一、移动端基础概念
1.1、英寸
一般用英寸描述屏幕的物理大小,如电脑显示器的**17、22,手机显示器的4.8、5.7**等使用的单位都是英寸。
需要注意,上面的尺寸都是屏幕对角线的长度:

英寸(inch,缩写为**in**)在荷兰语中的本意是大拇指,一英寸就是指甲底部普通人拇指的宽度。
英寸和厘米的换算:1英寸 = 2.54 厘米
1.2、像素
像素即一个小方块,它具有特定的位置和颜色。
图片、电子屏幕(手机、电脑)就是由无数个具有特定颜色和特定位置的小方块拼接而成。
像素可以作为图片或电子屏幕的最小组成单位。
下面我们使用sketch打开一张图片,将这些图片放大即可看到这些像素点:

通常我们所说的分辨率有两种,屏幕分辨率和图像分辨率
1)物理像素(分辨率)
屏幕分辨率
屏幕分辨率指一个屏幕具体由多少个像素点组成。下面是**apple**的官网上对手机分辨率的描述:

iPhone XS Max和iPhone SE的分辨率分别为2688 x 1242和1136 x 640。这表示手机分别在垂直和水平上所具有的像素点数,出厂就设定好的。
当然分辨率高不代表屏幕就清晰,屏幕的清晰程度还与尺寸有关。
图像分辨率
我们通常说的**图片分辨率其实是指图片含有的像素数**,比如一张图片的分辨率为**800 x 400。这表示图片分别在垂直和水平上所具有的像素点数为800和400**。
同一尺寸的图片,分辨率越高,图片越清晰。

PPI(屏幕)
PPI(Pixel Per Inch):每英寸包括的像素数。
**PPI**可以用于描述屏幕的清晰度以及一张图片的质量。
使用PPI描述图片时,PPI越高,图片质量越高,使用PPI描述屏幕时,PPI越高,屏幕越清晰。
在上面描述手机分辨率的图片中,我们可以看到:iPhone XS Max和iPhone SE的PPI分别为458和326,这足以证明前者的屏幕更清晰。
由于手机尺寸为手机对角线的长度,我们通常使用如下的方法计算PPI:
$$
\frac {\sqrt{水平像素点数^{2}+垂直像素点数^{2}}} {尺寸}
$$
**iPhone 6的PPI为:
$$
\frac {\sqrt{1334^{2}+750^{2}}} {4.7} =325.6
$$
那它每英寸约含有326**个物理像素点。
DPI(打印 )
DPI(Dot Per Inch):即每英寸包括的点数。
这里的点是一个抽象的单位,它可以是屏幕像素点、图片像素点也可以是打印机的墨点。
平时你可能会看到使用**DPI来描述图片和屏幕,这时的DPI应该和PPI是等价的,DPI最常用的是用于描述打印机,表示打印机每英寸可以打印的点数。**
一张图片在屏幕上显示时,它的像素点数是规则排列的,每个像素点都有特定的位置和颜色。
当使用打印机进行打印时,打印机可能不会规则的将这些点打印出来,而是使用一个个打印点来呈现这张图像,这些打印点之间会有一定的空隙,这就是DPI所描述的:打印点的密度。

在上面的图像中我们可以清晰的看到,打印机是如何使用墨点来打印一张图像。
所以,打印机的**DPI**越高,打印图像的精细程度就越高,同时这也会消耗更多的墨点和时间。
2)CSS像素(PX)
在写CSS时,我们用到最多的单位是**px,即CSS像素,当页面缩放比例为100%时,一个CSS像素等于一个设备独立像素。(width=device-width; initial-scale=1; 即页面缩放比为100%)**
但是**CSS像素是很容易被改变的,当用户对浏览器进行了放大,CSS像素会被放大,这时一个CSS像素**会跨越更多的物理像素,即元素的css像素数量不会改变,改变的只是每个css像素的大小
也就是说width: 128px的元素在缩放200%以后,宽度依然是128个css像素,只不过每个css像素的宽度和高度变为原来的两倍。如果原本元素宽度为128个设备独立像素,那么缩放200%以后元素宽度为256个设备独立像素(css像素宽度始终是128)。
页面的缩放系数 = CSS像素 / 设备独立像素
3)设备独立像素(重点)
智能手机发展非常之快,在几年之前,我们还用着分辨率非常低的手机,比如下面左侧的白色手机,它的分辨率是**320x480**,我们可以在上面浏览正常的文字、图片等等。
但是,随着科技的发展,低分辨率的手机已经不能满足我们的需求了。很快,更高分辨率的屏幕诞生了,比如下面的黑色手机,它的分辨率是**640x940**,正好是白色手机的两倍。
理论上来讲,在白色手机上相同大小的图片和文字,在黑色手机上会被缩放一倍,因为它的分辨率提高了一倍。这样,岂不是后面出现更高分辨率的手机,页面元素会变得越来越小吗?

然而,事实并不是这样的,我们现在使用的智能手机,不管分辨率多高,他们所展示的界面比例都是基本类似的。乔布斯在**iPhone4的发布会上首次提出了Retina Display**视网膜屏幕)的概念,它正是解决了上面的问题,这也使它成为一款跨时代的手机。

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

如果黑色手机使用了视网膜屏幕的技术,那么显示结果应该是下面的情况,比如列表的宽度为300个像素,那么在一条水平线上,白色手机会用300个物理像素去渲染它,而黑色手机实际上会用600个物理像素去渲染它。

我们必须用一种单位来同时告诉不同分辨率的手机,它们在界面上显示元素的大小是多少,这个单位就是设备独立像素(Device Independent Pixels)简称**DIPs或DP。上面我们说,列表的宽度为300个像素,实际上我们可以说:列表的宽度为300**个设备独立像素。
至于为什么出现设备独立像素这种虚拟像素单位概念,下面举个例子:
iPhone 3GS 和 iPhone 4/4s 的尺寸都是 3.5 寸,但 iPhone 3GS 的分辨率是 320x480,iPhone 4/4s 的分辨率是 640x960,这意味着,iPhone 3GS 有 320 个物理像素,iPhone 4/4s 有 640 个物理像素。
如果我们按照真实的物理像素进行布局,比如说我们按照 320 物理像素进行布局,到了 640 物理像素的手机上就会有一半的空白,为了避免这种问题,就产生了虚拟像素单位
iPhone 3GS 和 iPhone 4/4s 都是 320 个虚拟像素,在一条水平线上,在 iPhone 3GS 上,最终 1 个虚拟像素换算成 1 个物理像素。在 iphone 4s 中,1 个虚拟像素最终换算成 2 个物理像素,即用2个物理像素才能将1个虚拟像素渲染满。
这是如何做到的呢?对于那些像素密度高的屏幕,将多个设备像素划分为一个逻辑像素。至于将多少设备像素划分为一个逻辑像素,这由操作系统决定。
打开**chrome的开发者工具,我们可以模拟各个手机型号的显示情况,每种型号上面会显示一个尺寸,比如iPhone X显示的尺寸是375x812,实际iPhone X**的分辨率会比这高很多,这里显示的就是设备独立像素。

所以说,1 个虚拟像素被换算成几个物理像素,这个数值我们称之为设备像素比,也就是下面介绍的dpr
在javaScript中可以通过window.screen.width/ window.screen.height 查看
screen.width: 屏幕宽度
4)设备像素比(DPR)
devicePixelRatio,简称为 DPR,用来描述物理像素与设备独立像素的比例,其值等于 “物理像素 / 设备独立像素”。
在javascript中,可以通过js获取到当前设备的dpr:
1 | window.devicePixelRatio |
在css中,可以使用媒体查询**min-device-pixel-ratio,区分dpr**:
1 | @media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2){ } |
可知,iPhone6物理像素(分辨率)宽高为 750×1334,设备独立像素宽高为375×667,750=375 * 2,1334=667 * 2,所以 iPhone6 的 DPR 为 2。
反之,iPhone6的设备宽度和高度为375px * 667px,可以理解为设备的独立像素;而其dpr为2,根据上面公式,我们可以很轻松得知其物理像素(分辨率)为750px * 1334px。
计算公式如下:
当设备像素比为1:1时,使用1(1×1)个设备像素显示1个CSS像素
当设备像素比为2:1时,使用4(2×2)个设备像素显示1个CSS像素
当设备像素比为3:1时,使用9(3×3)个设备像素显示1个CSS像素
如下图所示:

当然,上面的规则也有例外,iPhone 6、7、8 Plus的实际物理像素是1080 x 1920,在开发者工具中我们可以看到:它的设备独立像素是**414 x 736,设备像素比为3,设备独立像素和设备像素比的乘积并不等于1080 x 1920,而是等于1242 x 2208**。
实际上,手机会自动把**1242 x 2208个像素点塞进1080 * 1920个物理像素点来渲染,我们不用关心这个过程,而1242 x 2208被称为屏幕的设计像素。我们开发过程中也是以这个设计像素**为准。
实际上,从苹果提出视网膜屏幕开始,才出现设备像素比这个概念,因为在这之前,移动设备都是直接使用物理像素来进行展示。
紧接着,**Android**同样使用了其他的技术方案来实现DPR大于1的屏幕,不过原理是类似的。由于Android屏幕尺寸非常多、分辨率高低跨度非常大,不像苹果只有它自己的几款固定设备、尺寸。所以,为了保证各种设备的显示效果,Android按照设备的像素密度将设备分成了几个区间:

当然,所有的**Android**设备不一定严格按照上面的分辨率,每个类型可能对应几种不同分辨率,所以,每个Android手机都能根据给定的区间范围,确定自己的DPR,从而拥有类似的显示。当然,仅仅是类似,由于各个设备的尺寸、分辨率上的差异,设备独立像素也不会完全相等,所以各种Android设备仍然不能做到在展示上完全相等。
5)视网膜屏(Retina)
苹果公司在2010年的WWDC上发布了iPhone4,第一次向世人展示了retina屏幕,并在后续发布了一系列r屏产品。
所谓“Retina”是一种显示技术,可以将更多的像素点压缩至一块屏幕里,从而达到更高的分辨率并提高屏幕显示的细腻程度。这种分辨率在正常观看距离下足以使人肉眼无法分辨其中的单独像素。
让多个物理像素渲染一个独立像素只是Retina屏幕为了达到效果而使用的一种技术。而不是所有DPR > 1的屏幕就是Retina屏幕。
比如:给你一块超大尺寸的屏幕,即使它的PPI很高,DPR也很高,在近距离你也能看清它的像素点,这就不算Retina屏幕。

受 windows操作系统的影响,多数人心中会产生这样的逻辑:分辨率越高,字会显得越小。
这是因为整个屏幕的面积主要由像素面积构成,即在相同面积下,分辨率越高,像素数量也越多,单个像素的面积也一定越小。于是高分辨率就对应着小像素,而像素小,则由像素构成的文字就小。所以就有了『分辨率越高,字会显得越小』的印象。
可是,苹果的r屏产品并没有让显示的文字变得很小,这又是怎么回 事呢?既然苹果产品的2倍r屏所包含的像素数量约为同面积非r屏的4倍,即每个像素尺寸约为非r屏像素的1/4,那为什么显示出来的文字没有缩减成1/4呢?
这是因为操作系统的HiDPI渲染方式,它决定了一个文字或图标最终以多少个像素来表达。2倍 r屏它在表达屏幕上每个元素时,都使用了4倍于非r屏的像素个数。这样算下来,像素尺寸为1/4,像素数目为4倍,于是最终渲染出来的文字和图标的大小与非r屏的相当。
举例说明,对一个标准屏来说,渲染一个宽高 2px*2px 的盒子将会使用 2px* 2px 物理像素,如我们的普通电脑屏;但是对于一个 2 倍屏来说,渲染一个 2px*2px 的盒子将会使用 (2px* 2) *(2px* 2) 物理像素,即宽高都放大了2倍,也就是1个css像素对应 4个物理像素(1:4),反向理解就是
如我们的 iPhone 4、5、6、7,如下图:

1.3、视口viewport
视口(viewport)就是浏览器显示页面内容的屏幕区域。视口可以分为布局视口、视觉视口和理想视口。
1)布局视口 layout viewport
一般移动端设备的浏览器都默认了一个布局视口,用于解决早期pc端页面在手机端显示的问题。
在PC浏览器上,布局视口就等于当前浏览器的窗口大小(不包括borders, margins, 滚动条)。 在移动端ios、安卓基本都将这个视口分辨率设置为980px,所以pc上网页大多都能在手机上展示,不过页面元素看上去很小,一般默认可以通过手动缩放网页。
在创建一个新的html文件的时候,默认会帮忙适配视口的,所以想要了解布局视口和视觉视口的关系的话,需要把默认适配的代码先注释掉

给网页添加一个宽100px,高100px的div盒子。

在pc端的效果:

没有问题,和理想中的情况差不多,来看一下移动端的效果

在PC端的网页在移动端显示的100px也太小了呀,这是为什么?
- 第一,它会按照宽度为980px来布局一个页面的盒子和内容;
- 第二,为了显示可以完整的显示在页面中,对整个页面进行缩小;
所以我们在PC端进行布局的时候会相对于980px进行布局,这个默认980px的视口称为布局视口

我们可以通过调用document.documentElement.clientWidth | document.body.clientWidth来获取布局视口大小 不包含滚动条
2)视觉视口 visual viewport
如果默认情况下,我们按照980px显示内容,那么右侧有一部分区域 就会无法显示,所以手机端浏览器会默认对页面进行缩放以显示到用 户的可见区域中;
那么显示在可见区域的这个视口,就是视觉视口。简单来说就是我们人眼能看到的可视区域大小,默认等于浏览器的宽度

当用户对浏览器进行缩放时,不会改变布局视口的大小,所以页面布局是不变的,但是缩放会改变视觉视口的大小。 比如:将浏览器窗口放大200%,css像素会随着视觉视口的放大而放大,这时一个css像素会跨越更多物理像素。
所以,布局视口会限制你的CSS布局而视觉视口决定用户具体能看到什么。
我们可以通过调用window.innerWidth来获取视觉视口大小。
innerWidth: 返回窗口的文档显示区的宽度,如果有水平滚动条,也包括滚动条高度。
3)理想视口 ideal viewport
布局视口在移动端展示的效果并不是一个理想的效果,能不能让布局视口和视觉视口相同呢?反正在移动端看到的视口只有那么大,如果视口相同了,就好布局了。下面引入理想视口的概念。

如果所有的网页都按照980px在移动端布局,那么最终页面都会被缩放显示。
- 事实上这种方式是不利于我们进行移动的开发的,我们希望的是设置100px,那么显示的就是100px;
- 如何做到这一点呢?通过设置理想视口(ideal viewport);
我们可以对布局视口(layout viewport)进行宽度和缩放的设置,以满足正常在一个移动端窗口的布局,就是布局视口和视觉视口的大小相同。
页面的缩放系数 = CSS像素/设备独立像素,实际上 = 理想视口宽度/视觉视口更准确。当页面缩放比例为100%时,CSS像素 = 设备独立像素,理想视口 = 视觉视口
我们可以通过调用window.screen.width来获取理想视口大小。
4)Meta Viewport
<meta> 元素用于提供关于文档的元信息,其中name属性用于定义元信息的名称,content属性用于定义元信息的值。
我们可以借助<meta>元素的viewport来帮助我们设置视口、缩放等,从而让移动端得到更好的展示效果。
1 | <meta name="viewport" content="width=device-width; initial-scale=1; maximum-scale=1; minimum-scale=1; user-scalable=no;"> |
Value | 可能值 | 描述 |
|---|---|---|
width | 正整数或device-width | 以pixels(像素)为单位, 定义布局视口的宽度。device-width: device 即设备的意思,就是设备宽度也就是视觉宽度,即将布局视口的宽度设置为设备宽度大小 |
height | 正整数或device-height | 以pixels(像素)为单位, 定义布局视口的高度。没有浏览器用过 |
initial-scale | 0.0 - 10.0 | 定义页面初始缩放比率,相对于理想视口缩放,进行100%的缩放,也就是缩放值为1。一般设置为1的效果和device-width是一样的 |
minimum-scale | 0.0 - 10.0 | 定义缩放的最小值;必须小于或等于maximum-scale的值。 |
maximum-scale | 0.0 - 10.0 | 定义缩放的最大值;必须大于或等于minimum-scale的值。 |
user-scalable | 一个布尔值(yes或者no) | 如果设置为 no,用户将不能放大或缩小网页。默认值为 yes。 |
width=device-width
content= “width=device-width”
当前的viewport宽度设置为 ideal viewport(理想视口) 的宽度
要得到 理想视口(ideal viewport)就必须把默认的布局视口( layout viewport)的宽度设为移动设备的屏幕宽度。因为meta viewport中的width能控制 布局视口(layout viewport)的宽度,所以我们只需要把width设为width-device这个特殊的值就行了。
1 | <meta name="viewport"content="width=device-width"> |
下图是这句代码在各大移动端浏览器上的测试结果:

可以看到通过width=device-width,所有浏览器都能把当前的viewport宽度变成ideal viewport的宽度,但要注意的是,在iphone和ipad上,无论是竖屏还是横屏,宽度都是竖屏时 理想视口 的宽度。
initial-scale=1
content= “initial-scale=1“
1 | <meta name="viewport"content="initial-scale=1"> |
这句代码也能达到和前一句代码一样的效果,也可以把当前的的viewport变为 理想视口。
从理论上来讲,这句代码的作用只是不对当前的页面进行缩放,也就是页面本该是多大就是多大。那为什么会有 width=device-width 的效果呢?
首先你得弄明白这个缩放是相对于什么来缩放的,因为这里的缩放值是1,也就是没缩放,但却达到了 理想视口 的效果,所以,缩放是相对于 理想视口 来进行缩放的,当对 理想视口 进行100%的缩放,也就是缩放值为1的时候,不就得到了 理想视口 吗?
下图是这句代码在各大移动端浏览器上的测试结果:

测试结果表明 initial-scale=1 也能把当前的viewport宽度变成 理想视口 的宽度,但这次轮到了windows phone 上的IE 无论是竖屏还是横屏都把宽度设为竖屏时 理想视口 的宽度。
所以,最完美的写法应该是,两者都写上去,这样就 initial-scale=1 解决了 iphone、ipad的毛病,width=device-width则解决了IE的毛病:
1 | <meta name="viewport" content="width=device-width; initial-scale=1;"> |
width和initial-scale
width 和 initial-scale 同时出现,并且出现了冲突
比如:<meta name="viewport" content="width=400, initial-scale=1">
width=400表示把 布局视口(layout viewport) 的宽度设为400px,initial-scale=1则表示把 布局视口(layout viewport) 的宽度设为 理想视口(ideal viewport) 的宽度(不一定是400px),那么浏览器到底该服从哪个命令呢?
布局视口宽度取的是width和视觉视口宽度的最大值!(重点)
例如:若在手机的理想视口宽度是320的前提下:
设置**width=400, initial-scale=1,此时布局视口的宽度会取400px(视觉视口宽度 = 理想视口宽度 / initial-scale **即320 / 1 = 320,400>320即取400px)
设置**width=400, initial-scale=0.5,此时布局视口的宽度会取640px(视觉视口宽度 = 理想视口宽度 / initial-scale **即320 / 0.5 = 640,400<640即取640px)

1 | <meta name="viewport" content="width=device-width, initial-scale=0.5"> |
如图中所示,在iPhoneX中,理想视口的宽度 和 设备宽度 都是**375px(screen.width),此时设置initial-scale为0.5,则此时的视觉视口的宽度为 375 / 0.5 = 750。所以,从图上也可以看到视觉视口**(window.innerWidth)是750px,同时布局视口(document.documentElement.clientWidth)会取375px和 750px中的最大值,即750px作为宽度值。
最完美的写法应该是:—-布局视口等于设备宽度(视觉视口宽度)
1 | <meta name="viewport" content="width=device-width; initial-scale=1;"> |
initial-scale的默认值
视觉视口宽度 = 理想视口宽度 / 当前缩放值
当前缩放值 = 理想视口宽度 / 视觉视口宽度
现在再来说下initial-scale的默认值问题,就是不写这个属性的时候,它的默认值会是多少呢?
很显然不会是1,因为当 initial-scale = 1 时,当前的布局视口宽度会被设为 理想视口宽度,但前面说了,各浏览器默认的 布局视口宽度一般都是980px,1024px,800px等等这些值,没有一开始就是 理想视口的宽度,所以 initial-scale的默认值肯定不是1。
**安卓设备上的initial-scale**默认值好像没有方法能够得到,或者就是干脆它就没有默认值,一定要你显示的写出来这个东西才会起作用,我们不管它了,这里我们重点说一下iphone和ipad上的initial-scale默认值。
根据测试,我们可以在iphone和ipad上得到一个结论:
在iphone和ipad上,无论你给viewport设的宽是多少,如果没有指定默认的缩放值,则iphone和ipad会自动计算这个缩放值,以达到当前页面不会出现横向滚动条(或者说viewport的宽度就是屏幕的宽度)的目的。
5)视口小结
首先如果不设置meta viewport标签,那么移动设备上浏览器默认的宽度值为800px,980px,1024px等这些**(大部分是980px)**,总之是大于屏幕宽度的。这里的宽度所用的单位px都是指css中的px,它跟代表实际屏幕物理像素的px不是一回事。
第二、每个移动设备浏览器中都有一个理想的宽度,这个理想的宽度是指css中的宽度,跟设备的物理宽度没有关系,在css中,这个宽度就相当于100%所代表的那个宽度。我们可以用meta标签把viewport的宽度设为那个理想的宽度,如果不知道这个设备的理想宽度是多少,那么用**device-width这个特殊值就行了,同时initial-scale=1**也有把viewport的宽度设为理想宽度的作用。所以,我们可以使用 <meta name="viewport" content="width=device-width, initial-scale=1"> 来得到一个理想的viewport(也就是前面说的ideal viewport)。
1.4、常见问题
1)图片虚化问题
当一个位图在标准屏显示时,一位图像素对应的就是一物理像素,这样就保证了一个完全保真的显示。但是当在 2 倍屏下时,物理像素面积是图像素面积的4倍(css 中的1设备独立像素相当于2物理像素),图片需要要放大四倍(宽高各两倍)来保持相同的物理像素的大小,这样就会丢失很多细节,造成失真的情形,也就是我们常说的图片虚化问题。

那么怎么解决该问题呢?
我们可以把要使用的图片扩大一倍,如要用的图片大小为 2px × 2px,我们可以使用 4px × 4px 的图片然后设置图片大小为 2px × 2px,这样对于2倍屏则正好,而标准屏则减少像素取样(一定程度上的浪费),现在设计直接提供二倍图即4px × 4px,之后我们将图片设置2px × 2px即可。

背景图:
a、采用媒体查询的方式
1 | .avatar{ |
b、使用-image-set
1 | .avatar { |
img标签:
a、使用img的srcset属性,浏览器会根据像素密度匹配显示适当的照片
1 | <img src="conardLi_1x.png" srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x"> |
b、用base64或是svg格式的图片
1 | <img src="conardLi.svg"> |
2)1px问题
我们先看一个demo,截图如下(iPhone 6截图):

如果我们把上图与我们手机系统上的 1px 边框进行对比,如下图:

我们会发现,上面两个上下线条,下线条的粗细才是正确的,上线条就显得有点粗了。但是上线条我们是用纯正的 1px border生成的,而下线条我们实际是采用transform压缩了1px高度的一半模拟实现的,也就相当于 0.5px 的高度了。
为了适配各种屏幕,我们写代码时一般使用设备独立像素来对页面进行布局。
而在设备像素比大于1的屏幕上,我们写的1px实际上是被多个物理像素渲染,这就会出现1px在有些屏幕上看起来很粗的现象。
以下举例解决1px问题。
1、viewport + rem 实现
**优点:**所有场景都能满足,一套代码,可以兼容基本所有布局
**缺点:**老项目修改代价过大,只适用于新项目
通过设置缩放,让CSS像素等于真正的物理像素。
当设备像素比为3时,我们将页面缩放1/3倍,这时1px等于一个真正的屏幕像素。
1 | const scale = 1 / window.devicePixelRatio; |
实际上,上面这种方案是早先flexible采用的方案,大概是通过判断DPR去动态生成viewport的mate标签
在DPR = 2 时,输出viewport:
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
在DPR = 3时,输出viewport:
<meta name="viewport" content="initial-scale=0.3333, maximum-scale=0.3333, minimum-scale=0.3333, user-scalable=no">
2、伪类 + transform 实现
原理是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并 transform 的 scale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。
**优点:**所有场景都能满足,支持圆角(伪类和本体类都需要加border-radius)
**缺点:**对于已经使用伪类的元素(例如clearfix),可能需要多层嵌套
1 | .scale-1px{ |
二、pc端扩展
2.1、viewpoet
viewport的功能在于控制你网站的最高块状(block)容器:<html>元素。
听起来有点玄乎,举个例子~假设你定义了一个可变尺寸的布局,且你定义一个侧边栏的宽度为width: 10%。当你改变浏览器窗口大小时,该侧边栏会自动扩张和收缩。这是什么原理呢?技术上讲,原理是侧边栏的宽度为它父元素宽度的10%,我们设它的父元素是且你未指定宽度。那么问题就变为了<body>的宽度到底是多少?
通常,一个块级元素占有起父元素的100%的宽度(这里有异常情况,暂时忽略)。所以<body>的宽度就是其父元素<html>的宽度。
**那么<html>元素到底有多宽?**因为它的宽度恰好为浏览器的宽度。所以你的侧边栏宽度width: 10%会占用10%的浏览器宽度。所以的web开发人员都直观的知道和使用该特性了。 但是你也许不知道原理。在原理上,<html>的宽度受viewport所限制,<html>元素为viewport宽度的100%。
反过来,viewport是严格的等于浏览器的窗口:定义就是如此。viewport不是一个HTML的概念,所以你不能通过CSS修改它。它就是为浏览器窗口的宽度高度 – 在桌面浏览器上如此,移动设备浏览器上有点复杂。
影响
缩放事件有一些奇怪的影响,你可以在本站上实验。页面滚动到最上面,放大浏览器2-3倍,网站的宽度会超过浏览器窗口。再将页面滚动到最右边,你会发现网站最上面的蓝色栏目不再对齐了。如图
这个效果反应了viewport是如何被定义的。我定义了最上面蓝色栏目的宽度为width: 100%。什么的100%?当然是html的宽度,同样是viewport的宽度,同样是浏览器窗口的宽度。
**重点:**缩放比例100%的情况下很正常,现在我们放大浏览器,viewport变得比网站的总宽度更小。对viewport无影响,但页面的内容溢出了<html>元素,但它却有属性overflow: visible。意味着溢出的部分依然会被显示。
但蓝色栏目却不会溢出。我定义了它宽度为width: 100%,结果浏览器为他赋值宽度为viewport的宽度。浏览器不会在乎这个栏目的宽度是不是过窄了。如图

2.2、小结

1 | 布局视口/视口viewport:document.documentElement.clientWidth/clientHeight | document.body.clientWidth/clientHeight(包括内边距,但不包括滚动条,边框和外边距) |
1)度量viewport
document. documentElement. clientWidth/Height
你也许想要知道viewport的尺寸,他们可以通过document. documentElement. clientWidth/Height来获取。如图

如果你熟悉DOM,你会知道document.documentElement实际上就是<html>元素:HTML文档的根元素。然而viewport是比<html>更高级别的元素,打个比喻,它是容纳<html>元素的元素。
2)度量HTML
document. documentElement. offsetWidth/Height

这个特性对真实的让你访问块级元素<html>元素,如果你为<html>元素赋值了宽度,offsetWidth会真实的反应出来。
3)滚动移位
window.pageXOffset/pageYOffset

4)事件坐标
pageX/Y:从<html>原点到事件触发点的CSS的 pixels
clientX/Y:从viewport原点(浏览器窗口)到事件触发点的CSS的 pixels
screenX/Y:从用户显示器窗口原点到事件触发点的设备 的 pixels

三、移动端开发选择
3.1、单独制作移动端页面(主流)
通常情况下,网址域名前面加 m(mobile) 可以打开移动端。通过判断设备,如果是移动设备打开,则跳到移动端页面。也就是说,做PC端和移动端两套网站代码,pc端是pc端的样式,移动端再写一套,专门针对移动端适配的一套网站。
3.2、响应式页面兼容移动端(其次)
pc和移动端共用一套网站,只不过在不同屏幕下,样式会自动适配一套代码适配多个终端(用户体验不好)

四、移动端技术解决方案
4.1、移动端浏览器兼容问题
- 移动端浏览器基本以webkit内核为主,因此我们就考虑webkit兼容性问题。
- 我们可以放心使用H5标签和CSS3样式。
- 同时我们浏览器的私有前缀我们只需要考虑添加-webkit-即可。

4.2、移动端CSS初始化
移动端 CSS 初始化推荐使用 normalize.css
官网地址: http://necolas.github.io/normalize.css/
4.3、CSS3盒模型
传统模式宽度计算:盒子的宽度 = CSS中设置的width + border + padding
CSS3盒子模型: 盒子的宽度= CSS中设置的宽度width 里面包含了 border 和 padding
也就是说,我们的CSS3中的盒子模型, padding 和 border 不会撑大盒子
1 | /*CSS3盒子模型*/ |
- 移动端可以全部CSS3 盒子模型
- PC端如果完全需要兼容,我们就用传统模式,如果不考虑兼容性,我们就选择 CSS3 盒子模型
4.4、移动端特殊样式(去除样式)
1 | /*CSS3盒子模型*/ |
五、移动端常见布局方式
5.1、流式布局(百分比布局)
- 流式布局,就是百分比布局,也称非固定像素布局。
- 通过盒子的宽度设置成百分比来根据屏幕的宽度来进行伸缩,不受固定像素的限制,内容向两侧填充。
- 流式布局方式是移动web开发使用的比较常见的布局方式。

注意事项
制作过程中,需要定义页面的最大和最小支持宽度。
- max-width 限制页面最大宽度
- max-height 限制页面最大高度
- min-width 限制页面最小宽度 当小于最小宽度就出现滚动条
- min-height 限制页面最小高度
5.2、flex弹性布局(推荐)
1)传统布局和flex布局对比
传统布局
- 兼容性好
- 布局繁琐
- 局限性,不能在移动端很好的布局
flex布局
- 操作方便,布局极其简单,移动端使用比较广泛
- pc端浏览器支持情况比较差
- IE11或更低版本不支持flex或仅支持部分
注意:
如果是pc端页面布局,还是采用传统方式
如果是移动端或者是不考虑兼容问题的pc端,则采用flex布局
2)flex布局原理
- 当我们为父盒子设为 flex(display=“flex”;) 布局以后,子元素的 float、clear 和 vertical-align 属性将失效。块级元素会在同一行显示。
- flex布局又叫伸缩布局 、弹性布局 、伸缩盒布局 、弹性盒布局。
- 采用 Flex 布局的元素,称为 Flex 容器(flexcontainer),简称”容器”。它的所有子元素自动成为容器成员,称为 Flex 项目(flexitem),简称”项目”。

总结:
- 就是通过给父盒子添加flex属性,来控制子盒子的位置和排列方式
- 给父盒子添加display=”flex”就可以直接改父级里面的行内元素的宽高
3)flex布局常见属性
父项常见属性
| 属性 | 描述 |
|---|---|
| flex-direction | 设置主轴的方向 |
| justify-content | 设置主轴上的子元素排列方式 |
| flex-wrap | 设置子元素是否换行 (子元素的大小小于父盒子的大小是换行不了的) |
| align-items | 设置侧轴上的子元素排列方式(单行) |
| align-content | 设置侧轴上的子元素的排列方式(多行) |
| flex-flow | 复合属性,相当于同时设置了 flex-direction 和 flex-wrap |
flex-direction
主轴方向
在 flex 布局中,是分为主轴和侧轴两个方向,同样的叫法有 : 行和列、x 轴和y 轴。
- 默认主轴方向就是 x 轴方向,水平向右
- 默认侧轴方向就是 y 轴方向,水平向下

主轴和侧轴是会变化的,就看 flex-direction 设置谁为主轴,剩下的就是侧轴。而我们的子元素是跟着主轴来排列的
| 属性值 | 描述 |
|---|---|
| row | 默认值,从左往右 |
| row-reverse | 从右往左 |
| column(常用) | 从上往下 |
| column-reverse | 从下往上 |
justify-content
主轴上的子元素排列方式
| 属性值 | 描述 |
|---|---|
| flex-start | 头部对齐,如果主轴是x轴,则左对齐(默认)。 如果主轴是y轴,则顶部对齐 |
| flex-end | 尾部对齐,如果主轴是x轴,则右对齐。 如果主轴是y轴,则底部对齐 |
| center | 在主轴居中对齐(主轴为x 水平居中,主轴为y 垂直居中) |
| space-around | 每个元素分配周围相同的空间 |
| space-between | 先两边贴边,再每个元素分配周围相同的空间 |
| space-evenly | 每个元素之间的间隔相等 |

flex-wrap
是否换行
默认情况下,项目都排在一条线(又称”轴线”)上。flex-wrap属性定义,flex布局中默认是不换行的。
如果多个子元素的大小大于父盒子的大小,则会缩小子元素大小,占一行。只有换行才能到第二行。
| 属性值 | 描述 |
|---|---|
| nowrap | 默认值,不换行 |
| wrap | 换行 |
align-items
侧轴上的子元素排列方式(单行)
该属性是控制子项在侧轴(默认是y轴)上的排列方式 在子项为单项(单行)的时候使用
| 属性值 | 描述 |
|---|---|
| flex-start | 元素位于容器的开头 |
| flex-end | 元素位于容器的结尾 |
| center | 垂直居中显示 |
| stretch | 默认值,拉伸(子盒子不要给高度才有效果) |
| baseline | 基线对齐 |

align-content
侧轴上的子元素的排列方式(多行)
只能用于子项出现 换行且flex容器设置了高度 的情况(多行),在单行下是没有效果的。
| 属性值 | 描述 |
|---|---|
| flex-start | 在侧周的头部开始排序 |
| flex-end | 在侧周的尾部开始排序 |
| center | 在侧轴的中间显示 |
| space-around | 每个元素周围分配相同的空间 |
| space-between | 首个元素放置于起点,末尾元素放置于终点 (重点) 两端对齐中间平分 |
| stretch | 默认值,设置子项元素高度平分元素高度(子盒子不要给高度才有效果) |
flex-flow
flex-flow 属性是 flex-direction 和 flex-wrap 属性的复合属性
1 | flex-flow: row wrap |
子项常见属性
flex 属性
| 属性 | 属性值 |
|---|---|
| flex | 0 默认值,定义子项目分配剩余空间,用flex来表示占多少份数。 |
1 | <style> |

上述代码表示:flex:1即三个div盒子分配section盒子的空间,每个盒子都占一份空间,也就是说每个盒子分配的空间站总空间的三分之一。
每个盒子占总空间比例: 1 ÷ 3 = 1/3
在上述代码基础上将第二个div盒子分配了三分空间
1 | section div { |

上述第一个div盒子占一份,第二个div占3份,第三个div占1份,一共5份。第一个盒子占了1/5,第二个盒子占3/5,第三个盒子占1/5
得出公式:
某子项占总空间的比例 = 某子项的flex值 ÷ 所有子项的flex值的总和
注意
子项是行内块元素,其宽度小于父元素的时候 flex:1; 会铺满 如果行内块元素的宽度大于容器 则不会铺满(子元素img不会进行收缩的)
align-self
控制子项自己在侧轴上的排列方式
align-self 属性允许某个子项有与其他子项有不一样的对齐方式,可覆盖 align-items 属性。默认值为 auto,表示继承父元素的 align-items 属性,如果没有父元素或是没高度,则等同于 stretch,该属性不生效。
未添加align-self
1 | <style> |

order
定义项目的排列顺序
默认值为0,以0位临界点。-1往前排列,1往后排列,以此类推。
1 | section div:nth-of-type(2) { |

4)flex布局案例
https://jihulab.com/shixna/demo/-/tree/flex%E5%B8%83%E5%B1%80%E6%A1%88%E4%BE%8B
5.3、rem布局
1)rem和em
1、em是参考的是自身字体大小,如果自身未设置字体大小,默认参考父元素的字体大小,父元素也没有就一直往上找,直到html
1 | <style> |
2、rem参考html元素的字体大小来计算的,即1rem = html的font-size。比如,根元素(html)设置font-size=12px,非根元素设置width:2rem, 则换成px表示就是24px。
1 | html { /* 根html 为 12px */ |
rem的优点就是可以通过修改html里面的文字大小来改变页面中元素大小,可以很好来控制整个页面的元素大小。
2)媒体查询
媒体查询(Media Query)是CSS3新语法。
- 使用 @media查询,可以针对不同的媒体类型定义不同的样式
- @media 可以针对不同的屏幕尺寸设置不同的样式
- 当你重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面
- 目前针对很多苹果手机、Android手机,平板等设备都用得到多媒体查询
语法规范
1 | @media mediatype and/not/only (media feature) {} |
| 属性 | 描述 |
|---|---|
| @media | 固定写法,用 @media开头 注意@符号 |
| mediatype | 媒体类型 **all:**用于所有设备;**print:**用于打印机和打印预览;**screen:**用于电脑屏幕,平板电脑,智能手机等 |
| and/not/only | 关键字 **and:**可以将多个媒体特性连接到一起,相当于“且”的意思。 **not:**排除某个媒体类型,相当于“非”的意思,可以省略。 **or:**可以测试多个媒体查询条件,只要有一个条件成立就执行,相当于“或”的意思。 **only:**指定某个特定的媒体类型,可以省略。 |
| media feature | 媒体特性,**width:**宽度;**min-width:**最小宽度;**max-width:**最大宽度 |
书写规则
为了防止混乱,媒体查询我们要按照从小到大或者从大到小的顺序来写,但是我们最喜欢的还是从小到大来写(min-width到max-width),这样代码更简洁。

案例示范
案例一:
屏幕小于540px时,背景为蓝色;屏幕大于等于540px并且小于970px的时候,背景为绿色;屏幕大于等于970的时候,背景为红色;
1 | @media screen and (max-width:539px) { |
案例二:
rem实现元素动态大小变化
1 | <style> |

不同分辨率的font-size值
首先假设我上面的页面设计稿给我时候是按照640的标准尺寸给我的前提下,(当然这个尺寸肯定不一定是640,可以是320,或者480,又或是375)来看一组表格。

页面是以640的宽度去切的,怎么计算不同宽度下font-site的值,大家看表格上面的数值变化应该能明白。
**举个例子:**384/640 = 0.6,384是640的0.6倍,所以384页面宽度下的font-size也等于它的0.6倍,这时384的font-size就等于12px。在不同设备的宽度计算方式以此类推。
媒体查询加载css文件
1 |
|
1 | /* style.css */ |
当小于320px的时候整个页面背景色为gold色,在320-640px之间为pink色,大于640ppx为skyblue色。
5.4、响应布局
1)响应式开发原理
就是使用媒体查询针对不同宽度的设备进行布局和样式的设置,从而适配不同设备的目的。
设备的划分情况:
| 设备划分 | 尺寸区间 |
|---|---|
| 超小屏幕(手机) | < 768px |
| 小屏设备(平板) | >= 768px ~ < 992px |
| 中等屏幕(桌面显示器) | >= 992px~ < 1200px |
| 宽屏设备(大桌面显示器) | >=1200px |
2)响应式布局容器
响应式需要一个父级作为布局容器,来配合子级元素来实现变化效果。
原理就是在不同屏幕下,通过媒体查询来改变这个布局容器的大小,再改变里面子元素的排列方式和大小,从而实现不同屏幕下,看到不同的页面布局和样式变化。
父容器版心的尺寸划分
- 超小屏幕(手机,小于 768px):设置宽度为 100%
- 小屏幕(平板,大于等于 768px):设置宽度为 750px (设置的宽度比屏幕宽度小 是因为让两侧留白更美观)
- 中等屏幕(桌面显示器,大于等于 992px):宽度设置为 970px
- 大屏幕(大桌面显示器,大于等于 1200px):宽度设置为 1170px
1 |
|

案例demo
1 |
|
1 | * { |

3)响应式布局框架—bootstrap
Bootstrap简介
Bootstrap 来自 Twitter(推特),是目前最受欢迎的前端框架。Bootstrap 是基于HTML、CSS 和 JAVASCRIPT 的,它简洁灵活,使得 Web 开发更加快捷。
框架:顾名思义就是一套架构,它有一套比较完整的网页功能解决方案,而且控制权在框架本身,有预制样式库、组件和插件。使用者要按照框架所规定的某种规范进行开发。
bootstrap优点
- 标准化的html+css编码规范
- 提供了一套简洁、直观、强悍的组件
- 有自己的生态圈,不断的更新迭代
- 让开发更简单,提高了开发的效率
版本简介
- 2.x.x:停止维护,兼容性好,代码不够简洁,功能不够完善。
- 3.x.x:目前使用最多,稳定,但是放弃了IE6-IE7。对 IE8 支持但是界面效果不好,偏向用于开发响应式布局、移动设备优先的WEB 项目。
- 4.x.x:最新版,目前还不是很流行
bootstrap基本使用
在现阶段我们还没有接触JS相关课程,所以我们只考虑使用它的样式库。
Bootstrap 使用四步曲:
1.创建文件夹结构
2.创建 html 骨架结构
1 |
|
3.引入相关样式文件
引入bootsrap的css样式就不要加公共样式了 它自带公共样式
1 | <!-- Bootstrap 核心样式--> |
4.书写内容
- 直接拿Bootstrap 预先定义好的样式来使用
- 修改Bootstrap 原来的样式,注意权重问题
- 学好Bootstrap 的关键在于知道它定义了哪些样式,以及这些样式能实现什么样的效果
bootstrap布局容器
Bootstrap 需要为页面内容和栅格系统包裹一个 .container 或者.container-fluid 容器,它提供了两个作此用处的类。
| .container | .container-fluid |
|---|---|
| 响应式布局的容器固定宽度 大屏 ( >=1200px) 宽度定为 1170px 中屏 ( >=992px) 宽度定为 970px 小屏 ( >=768px) 宽度定为 750px 超小屏 (100%) | 流式布局容器 百分百宽度 占据全部视口(viewport)的容器。 |
4)阿里百秀案例
5.5、扩展—less基础
Less(LeanerStyle Sheets 的缩写)是一门 CSS扩展语言,也成为CSS预处理器。
常见的CSS预处理器:Sass、Less、Stylus
一句话:Less是一门 CSS 预处理语言,它扩展了CSS的动态特性。
- less编译
- less变量
- less嵌套
- less运算
- less混入
1)less编译
本质上,LSs包含一套自定义的语法及一个解析器,用户根据这些语法定义自己的样式规则,这些规则最终会通过解析器,编译生成对应的CSS文件。所以,我们需要把我们的.less文件,编译生成为.css文件,这样我们的html页面才能使用。
推荐方法
1)基于node环境在线安装Less,使用cmd命令安装less。
1 | npm install -g less |
在当前文件夹,使用cmd命令“lessc style.less>style.css”
2)VScode中安装Easy LESS插件,只要保存一下less文件,会自动生成一个css文件。(推荐)
2)less变量
变量是指没有固定的值,可以改变的,因为我们css中的一些颜色和数值等经常使用。
语法:@变量名: 值;
变量命名规范:
- 必须有@为前缀
- 不能包含特殊字符
- 不能以数字开头
- 大小写敏感
1 | /* index.less */ |
3)less嵌套
1 | /* 常规写法 */ |
如果遇见 (交集|伪类|伪元素选择器) ,利用&进行连接
1 | /* 常规写法 */ |
4)less运算
任何数字、颜色或者变量都可以参与运算。就是Less提供了加(+)、减(-)、乘(*)、除(/)算术运算。
1 | /*Less 里面写*/ |
- 除法需要使用小括号括起来。例如:(100px / 5)
- 乘号(*)和除号(/)的写法
- 运算符中间左右有个空格隔开 1px + 5
- 对于两个不同的单位的值之间的运算,运算结果的值取第一个值的单位
- 如果两个值之间只有一个值有单位,则运算结果就取该单位
5)less混入
1 | /* less */ |
带括号的混入
如果你想创建一个 mixin 但你不希望那个 mixin 出现在你的 CSS 输出中,请在 mixin 定义之后加上括号。
1 | /* less */ |
带参数的混入
1 | /* less */ |
6)less文件之间的引入
1 | /* index.less */ |
六、移动端适配方案
6.1、媒体查询 + REM
设计稿常见尺寸宽度

一般情况下,我们以一套或两套效果图适应大部分的屏幕,放弃极端屏或对其优雅降级,牺牲一些效果。现在基本以750px为准。
- 假设设计稿是750px
- 我们把整个屏幕划分为10等份(划分标准不一可以是20份也可以是15等份)
- 每一份作为html字体大小,这里就是75px
- 那么在320px设备的时候,字体大小为320/10就是 32px
假设我们有一个设计稿,宽度为750px。我们将整个屏幕分为10等份,每份为75px,作为html字体大小的基准。现在我们有一个正方形的页面元素,宽度和高度都为150px。
在750px屏幕下,这个页面元素的宽度和高度分别为150/75=2rem,因此它是2rem×2rem。
在320px屏幕下,html字体大小为32px,因此2rem等于64px。这时该页面元素的宽度和高度也会变成64px。
这样,无论在何种屏幕尺寸下,页面元素都会按照设计稿中的比例进行等比缩放,确保了页面在不同设备上的一致性和美观性。
| 屏幕宽度 | HTML的FONT-SIZE | 盒子的宽度 |
|---|---|---|
| 750px | 750/10 = 75px | 4rem = 300px ÷ 75 |
| 640px | 640/10 = 64px | 4rem = 264px ÷ 64 |
| 480px | 480/10 = 48px | 4rem = 192px ÷ 48 |
| 320px | 320/10 = 32px | 4rem = 128px ÷ 32 |
总结
1 | 根元素html字体大小 = 屏幕宽度 / 划分的份数(自定义) |
1)媒体查询
通过媒体查询动态设置html字体大小,为了兼容低版本的浏览器,因为低版本的浏览器可能不兼容css3的媒体查询,所以会默认设置一个根目录的字体大小
1 | // 根据媒体查询,设置不同屏幕中的 html 的字号 |
2)px转 rem转换
1、自己封装一个scss函数
1 | // 以scss为例, 设计稿尺寸为750 函数指令 |
2、Vscode插件 cssrem
可将写的px自动转换成rem,插件默认font-size为16,我们需要手动修改。
以设计稿是750px为例,将页面划分为10等份,那么这里就设置为75=750/10;如果将页面划分为15等份,那么这里就设置为50=750/15;

输入px的时候会弹出转换后的rem

6.2、Flexible + REM
flexible有两个版本@0.3.2和@2.0两个版本分别对应github仓库的master和2.0分支也即对应lib-flexible和amfe-flexible
地址:https://github.com/amfe/lib-flexible
| 名称 | 版本 | 分支 | 特点 | 备注 |
|---|---|---|---|---|
| lib-flexible | flexible@0.3.2 | master | 解决1px问题 | 作为过渡方案,弃用 |
| amfe-flexible | flexible@2.0 | 2.0 | 建议使用 |
amfe-flexible是lib-flexible的升级方案 lib-flexible是一种过渡方案,不能与响应式布局兼容。由于viewport单位得到众多浏览器的兼容,上面这种方案现在已经被官方弃用,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用viewport来替代此方。amfe-flexible的缺点是字体大小适配问题。
1)lib-flexible(弃用)
核心
- 不需设置viewport 的
<meta>标签,根据dpr动态改写viewport 的<meta>标签的initial-scale,maximum-scale,minimum-scale。可解决1px问题 - 给
<html>元素添加data-dpr属性,并且动态改写data-dpr的值; - 给
<html>元素添加font-size属性,并且动态改写font-size的值 ;
优点:
- 无1px问题;
- 无字体在ios高倍屏下的自适应问题;
缺点:
该js插件对所有安卓设备都按照1dpr处理,导致在三星S8(dpr:4)的手机上整体显示较小;
不兼容响应式布局;
@media语法中涉及到的尺寸查询语句,查询的尺寸依据是当前设备的物理像素,和Flexible的布局理论(即针对不同dpr设备等比缩放视口的scale值,从而同时改变布局视口和视觉视口大小)相悖,因此响应式布局在“等比缩放视口大小”的情境下是无法正常工作的。在微信环境(或其他可设置字体大小的Web浏览器中,如Safari)下;
设置微信的字体大小(调大)后再打开使用Flexible技术适配的Web页面,你会发现页面布局错乱了,所有使用rem设置大小的元素都变大了,因为在调整Web浏览器的字体大小后,我们的”视口”也响应的等比缩小了,即视觉视口(window.innerWidth),豁然开朗,并不是我们的元素变大了,而是我们的视觉视口变小了!
扩展:
1、应用中,对字体大小不使用rem,rem的基准值与屏幕宽度成正比, 这就造成相同分辨率的屏幕,越宽字越大,越窄字越小,因此,在开发过程中,我们创造了 dpx(dpr px) 这个单位,,按照dpr来放大 1px, 2px, 3px 大小的字体,再按照屏幕dpr缩小,,这样就达到了字体不缩放,各种屏幕的字体看起来都差不多,也与屏幕宽度无关。
根据lib-flexible可以给<html>元素添加data-dpr属性,并且动态改写data-dpr的值,则可以用以下方法依据屏幕的dpr动态的给文字设置大小,amfe-flexible则无法实现。
1 | //定义一个mixin 根据不同dpr将px值转化成相应的dpr的px值。 |
2、边框一般不使用rem , 在移动设备上最常见的就是1px的边框, 由上一条规则我们知道rem无法精确到1px, 它只是一个与屏幕宽度有关的 模糊值。 已知在两种情况下无法满足我们的需求:
A、需求1物理像素的边框。
B、在屏幕比较窄的1dpr的安卓设备上,由于rem不能整除造成rem换算成px 不足 1px, 边框丢失。
2)amfe-flexible
核心
需设置viewport 的
<meta>标签;获取DPR,根据DPR动态的设置body的字体大小;
document.body.style.fontSize = (12 * dpr) + 'px'将屏幕划分为十份,并动态给html设置字体大小;
1
2
3
4
5var docEl = document.documentElement;
function setRemUnit() {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
优点:
- 没有给安卓的dpr固定设置为1;
- 兼容响应式布局;
缺点:
- 不能像lib-flexible给字体设置dpx单位,则会有字体大小适配问题(用rem影响不大);
- 不能像lib-flexible动态设置viewport 的
<meta>标签,解决1px问题;
扩展:
因为flexible.js是默认将屏幕分为10等分,但是当屏幕大于750的时候希望不要再去重置html字体了,所以要自己通过媒体查询设置一下。
1 | /* 如果我们的屏幕超过了 750px 那么我们就按照 750设计稿来走 不会让我们页面超过750px */ |
3)px转rem
postcss-pxtorem
以vue项目为例,在项目里面写px,项目运行时会自动转换成rem。
1 | // postcss.config.js中配置,若没有该文件就新建一个 |
6.3、vh、vw方案(推荐)
vw/vh这种方案是将视觉视口的宽度/高度分为100份,其实上面的flexible方案就是模仿这种方法,因为当时vm的兼容性不好,flexible就变成了一种过渡方案。
vw(Viewport's width):1vw等于视觉视口的1%vh(Viewport's height):1vh为视觉视口高度的1%vmin:vw和vh中的较小值vmax: 选取vw和vh中的最大值

从我们的实际开发工作出发,我们现在都是统一使用的iPhone6的视觉设计稿(即宽度为**750px),那么100vw=750px,即1vw = 7.5px。那么如果设计稿上某一元素的宽度为value像素,那么其对应的vw值则可以通过vw = value / 7.5**来计算得到。
这里的比例关系我们也不用自己换算,我们可以使用PostCSS的 postcss-px-to-viewport 插件帮我们完成这个过程。写代码时,我们只需要根据UI给的设计图写px单位即可。
缺点:
- 不需要 js 做适配
- 方案灵活技能实现整体缩放又能实现局部不缩放
缺点:
- px转换成vw不一定能整除,因此会有一定的像素差
- 比如当容器使用vw,margin采用px时,很容易造成整体宽度超过100vw,从而影响布局效果。对于这个问题,我们可以将margin换成padding,并且配合box-sizing解决。随着将来浏览器或者应用自身的WebView对calc()函数的支持之后,碰到vw和px混合使用的时候,可以结合calc()函数使用。
- 还是有1px和图片模糊的问题
1)px转vw/vh
postcss-px-to-viewport
1 | npm i postcss-px-to-viewport -D |
1 | // postcss.config.js |
2)降级处理不兼容
在我们已知的大部分主流浏览器中,都是天然支持vw单位的,但不排除有某些浏览器的某些版本存在不兼容的情况,如果业务需要,我们可以通过如下两种js库做降级处理:
- CSS Houdini:通过CSS Houdini针对vw做处理,调用CSS Typed DOM Level1提供的
CSSUnitValue API; - CSS Polifill:通过相应的Polyfill做响应的处理,目前针对vw单位的Polyfill主要有:
vminpoly、Viewport Units Buggyfill、vunits.js和Modernizr。大漠老师比较推荐的是 Viewport Units Buggyfill
Viewport Units Buggyfill
1、引入 JavaScript 文件
- viewport-units-buggyfill 主要有两个 JavaScript 文件:viewport-units-buggyfill.js 和 viewport-units-buggyfill.hacks.js。只需要在 HTML 文件中引入这两个文件。比如在 Vue 项目中的 index.html 引入它们:
1 | <script src="//g.alicdn.com/fdilab/lib3rd/viewport-units-buggyfill/0.6.2/??viewport-units-buggyfill.hacks.min.js,viewport-units-buggyfill.min.js"></script> |
备用地址:viewport-units-buggyfill.js
2、在 HTML 文件中调用 viewport-units-buggyfill
1 | <script> |
3、添加content:属性
在你的CSS中,只要使用到了viewport的单位地方,需要在样式中添加content:
1 | .my-viewport-units-using-thingie { |
这可能会令你感到恶心,而且我们不可能每次写vw都去人肉的计算。特别是在我们的这个场景中,我们使用了**postcss-px-to-viewport这个插件来转换vw**,更无法让我们人肉的去添加content内容。
这时候我们就需要结合**postcss-viewport-units插件,让插件自动去添加处理content:**属性
postcss-viewport-units
在postcss.config.js文件中配置如下代码之后,之后则会自动去添加处理**content:**属性
1 | plugins: { |
缺陷
使用了 Viewport Units Buggyfill 后,它会在占用 content 属性,因此会或多或少的造成一些副作用。如 img元素和伪元素的使用**::before** 或**::after**。
**1)**对于 img,在部分浏览器中,content 的写入会造成图片无法正常展示,这时候需要全局添加样式覆盖:
1 | img { |
**2)对于::before 等伪元素,就算是在里面使用了 vw 单位,Viewport Units Buggyfill 对其并不会起作用(添加content:**属性),如:
1 | // 编译前 |
七、适配iPhoneX
在iPhoneX发布后,许多厂商相继推出了具有边缘屏幕的手机。

这些手机和普通手机在外观上无外乎做了三个改动:圆角(corners)、刘海(sensor housing)和小黑条(Home Indicator),为了适配这些手机,产生了安全区域的概念。
安全区域:指的就是不受前面这几种情况的影响,把页面限制在安全区域内,使页面显示正常。

7.1、viewport-fit
从 iOS11 开始 ,为了适配刘海屏,Apple 公司对 HTML 的 viewport meta 标签做了扩展,让页面充满全屏
1 | <meta name="viewport" content="viewport-fit=cover"> |
viewport-fit 属性
默认情况下或者设置为auto和contain效果相同
| 值 | 含义 |
|---|---|
| contain | 可视窗口完全显示网页内容 (左图) |
| cover | 网页内容完全覆盖可视窗口 (右图) |
| auto | 默认值,跟 contain 表现一致 |

7.2、env、constant
我们需要将顶部和底部合理的摆放在安全区域内,iOS11新增了两个CSS函数env、constant,用于设定安全区域与边界的距离。
**注意:**constant在iOS < 11.2的版本中生效,env在iOS >= 11.2的版本中生效
函数的内部可以是四个常量:
safe-area-inset-left:安全区域距离左边边界距离
safe-area-inset-right:安全区域距离右边边界距离
safe-area-inset-top:安全区域距离顶部边界距离
safe-area-inset-bottom:安全区域距离底部边界距离
使用方法
这个函数必须是指定viewport-fit之后才可以使用
1 | <meta name="viewport" content="viewport-fit=cover"> |
constant在iOS < 11.2的版本中生效,env在iOS >= 11.2的版本中生效,这意味着我们往往要同时设置他们,将页面限制在安全区域内:
1 | // 使用 @supports 查询机型是否支持 constant() / env() 实现兼容代码隔离,个别安卓也会成功进入这个判断,因此加上-webkit-overflow-scrolling: touch的判断可以有效规避安卓机。 |
@supports() {} 扩展
CSS中的 @supports 用于检测浏览器是否支持 CSS 的某个属性。其实就是条件判断,如果支持某个属性可以写一套样式,如果不支持某个属性,可以提供另外一套样式作为替补。
语法
@supports 规则由一组样式声明和一条支持条件构成。支持条件由一条或多条使用 逻辑与(and)、逻辑或(or)、逻辑非(not)结合的名称 - 值对(name-value pair)组成。可以使用圆括号调整操作符的优先级。
1 | @supports(prop:value) { |
八、扩展
8.1、横屏适配
很多视口我们要对横屏和竖屏显示不同的布局,所以我们需要检测在不同的场景下给定不同的样式:

1)JavaScript检测横屏
window.orientation:获取屏幕旋转方向
1 | window.addEventListener("resize", ()=>{ |
2)CSS检测横屏
1 | @media screen and (orientation: portrait) { |
8.2、link和@import引入的区别
- **本质:**link属于 XHTML 标签,而 @import 完全是 CSS 提供的一种方式。
- 加载顺序: link在页面加载过程中,浏览器会同时加载页面内容和外部样式表,从而提高页面加载速度,非阻塞,而 @import 会在页面内容加载完成后再加载外部样式表,可能会导致页面加载速度变慢,阻塞。
- 兼容性: @import 是 CSS2.1 提出的,所以老的浏览器不支持,@import 只有在 IE5 以上的才能识别,而 link 标签无此问题。
- 使用dom控制样式:当使用 javascript 控制 dom 去改变样式的时候,只能使用 link 标签,因为@import 不是 dom 可以控制的。
