移动端适配
所谓移动端适配,其概念就是在任意手机中我们开发的网页显示都是正常的,如下例子所示
html
<div class="container"></div>
css
* {
margin: 0;
padding: 0;
}
.container {
width: 375px;
height: 50px;
background-color: red;
}
上面的页面如果在 iPhone6/7/8
中查看那就一切正常,因为 iPhone6/7/8
的设备宽度就是 375px
。
如果换到 iPhone6/7/8
Plus 中,由于设备宽度为 414px
,就会导致 375px
的宽度并不能占满整个屏幕,这时候就需要进行适配处理了。
因此移动端适配的任务就是要让我们的网页在各个设备中都能显示正常,包括字体、宽高、间距、图片等,常见的适配方案有很多,下面列出常用的 5 种适配方案:
- 百分比适配
- viewport 缩放适配
- DPR缩放适配
- rem适配
- vw、vh适配
百分比适配
在 CSS
中盒子的宽度可以设置为一个百分比值,表示根据父级宽度的百分比来计算宽度。因此我们可以通过百分比的方式让一个盒子在任何设备中宽度占比都是一样的。
html
<div class="container">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
css
.container {
display: flex;
width: 100%;
height: 50px;
}
.container div {
width: 25%;
height: 50px;
}
.container div:nth-child(1) {
background-color: red;
}
.container div:nth-child(2) {
background-color: yellow;
}
.container div:nth-child(3) {
background-color: blue;
}
.container div:nth-child(4) {
background-color: green;
}
在上面代码中,.container
下的四个 <div>
的宽度都为 25%,其相对的是父级宽度的百分比,所以在任何设备下显示都是占同样比例。
注意: 利用百分比布局,不是所有情况都根据父级宽度来进行计算,例如
- [max/min-]height、top、bottom 等: 取决于父级高度的百分比
- padding、margin四个方向: 取决于父级宽度的百分比
- transform: translate()、background-size 等: 取决于自身宽度的百分比
所以百分比布局的适配方案相对标准不统一,在开发时很容易造成意外的问题,这种方案往往需要配合其他适配方案一起使用。
viewport适配
由于在不同的设备上,CSS
像素是不一样的。例如 iPhone 6/7/8
为 375px
,而 iPhone 6/7/8 Plus
为 414px
。那么,我们可以通过设置 viewport
的缩放,来使页面显示正常。
这种适配方案的原理就是把所有机型的 CSS 像索(设备宽度)设置成一致的
html
<div class="container">
<div>123</div>
<div></div>
<div></div>
<div></div>
</div>
css
.container {
display: flex;
width: 375px;
height: 50px;
}
.container div {
width: 93.75px;
height: 50px;
}
.container div:nth-child(1) {
background-color: red;
}
.container div:nth-child(2) {
background-color: yellow;
}
.container div:nth-child(3) {
background-color: blue;
}
.container div:nth-child(4) {
background-color: green;
}
正常来说,上面的页面在 iPhone 6/7/8 Plus
是无法占满全屏的,因为宽度只有 375px
,而设备宽度是 414px
。
此时,我们就可以通过缩放 viewport
的形式来让网页显示正常,那么问题来了,缩放多少呢?
由于我们在编写网页时,页面的宽度是按照 375px
去开发的,因此缩放比应该按照 375px
去计算。计算缩放比的公式为:设备宽度 / 布局宽度 ,相当于 414 / 375 = 1.104
。但设备宽度是不能写死的,而是要根据当前设备去获取,然后再来计算缩放比。
于是我们在设置带有 name='viewport'
的 <meta>
标签,在 <head>
标签中添加如下的 JavaScript 代码:
js
(function () {
const view = document.querySelector('meta[name="viewport"]');
const targetWidth = 375;
// 获取设备宽度
const curWidth = document.documentElement.clientWidth;
const targetScale = curWidth / targetWidth;
view.content = `initial-scale=${targetScale},user-scalable=no,minimum-scale=${targetScale},maximum-scale=${targetScale}`;
})();
现在页面看起来是解决了问题,在各个设备下都能正常显示。但这种适配方案也有其本身的缺点,主要有三点:
- 在设置宽度时,要把宽度设置成一个固定值,那么所有手机看上去都是同样的大小,没有分别,不太好。厂商特意做出各种大小的手机,还要弄成一样,那买大屏机有什么意义。
- 算出的值在一些有小数的情况下可能会出现误差(可忽略),因为设备独立像素不能有小数。
- 对设计稿的测量存在问题。
DPR缩放适配
首先 DPR
指的是像素比(物理像素 / CSS
像素)。以 iPhone6/7/8
来举例,其物理像素为 750px
,CSS
像素(设备宽度)为375px
,DPR
为 2 。此时 UI 要给我们一张图占满整个屏幕的图,那么他应该按照 750px
起稿还是 350px
起稿?
关于设备像素的文章:一文带你深入了解移动端像素的概念
答案:应该是按照物理像素 750px
进行起稿。
- 当 DPR = 1 时,使用1个物理像素显示1 个
CSS
像素; - 当 DPR = 2 时,使用4个物理像素显示1 个
CSS
像素;
如上图所示,对于 retina
视网膜屏幕(2 倍屏)而言,1 个位图像素对应于 4 个物理像素,由于单个位图像素不能再进一步分割,所以只能就近取色,这也是导致图片模糊的原因。
对于图片高清的问题,比较好的方案就是使用 2 倍图,如 200x300
(css 像素) 的 <img>
标签,需要提供 400x600
的图片。
如此一来,位图像素点个数就是原来的4倍,在 retina
视网膜屏幕下,位图像素点个数就可以跟物理像素点个数形成 1:1
的比例,图片自然就清晰了。
问题:如果普通屏幕下也用了 2 倍图,会怎样呢?
在普通屏幕下,200×300
(css 像素)img标签,所对应的物理像素个数是 200×300
个,而 2 倍图的位图像素个数则是200×300x4
个,所以会出现一个物理像素点对应4个位图像素点。
所以取色过程也只能通过一定的算法(downsampling 算法)进行处理,显示结果就是一张只有原图像素总数四分之一的图片,肉眼看上去图片不会模糊,但是图片会缺少一些锐利度,或者是有色差(在接受范围内)。
那么基于 DPR 的缩放适配方式,其原理就是将 CSS 像素缩放成与设备像素一样大的尺寸
在实际开发中,设计者为了页面的高清,都是采用物理像素的值来进行设计。如 iPhone6/7/8
的设备宽度为 375px
,则将其缩放为 750px
。
那么使用 DPR
计算缩放比就是:缩放比 = 1 / DPR ---> 1 / (物理像素 / CSS像素) ---> 1 / (750 / 375)
js
(function () {
var view = document.querySelector('meta[name="viewport"]');
// 直接获取设备的DPR
var targetScale = 1 / window.devicePixelRatio;
view.content = `initial-scale=${targetScale},user-scalable=no,minimum-scale=${targetScale},maximum-scale=${targetScale}`;
})();
但是按照 DPR
缩放适配后反而占不满了,因为我们给的宽度为 375px
,因此只占了一半,那么这种适配方案的意义是什么呢?
实际上这种方案最大的意义就是开发者和设计者的像素都是统一的 ,因为 UI 是按照 750px
来设计的(iPhone6/7/8
为例),那么前端在量图的时候,也是以 750px
为基准。通过 DPR
适配,量出来多少写代码时就可以设置多少。
但这种方案并没有真正解决适配问题,因为设计稿是 750px
像素,前端测量出来的也是 750px
,但是如果将宽度设置为 750px
,在 iPhone 6/7/8 Plus
又存在适配问题了,那么就需要配合 rem
进行适配了。
rem适配
rem
适配也是移动端比较主流的一种适配方案
rem
全称 font size of the root element
,是 CSS3
新增的一个相对单位,含义是相对于根元素的字体大小的单位,它就是一个相对单位。
如下代码所示:
html
<div class="container"></div>
<p>Hello Rem</p>
css
:root {
font-size: 10px;
}
.container {
width: 20rem;
height: 20rem;
background-color: blue;
}
p {
font-size: 3rem;
}
在上面的示例中,将根元素的字体设置为10px
,其他元素都设置 rem
单位,表示根据根元素字体大小来缩放。例如 <p>
元素的字体大小是 3rem
,也就是 3×10px = 30px
;而 .container
的宽高为 20rem
,大小为 200x200px
rem适配的原理:
rem
适配的原理就是把设备宽度都分成相同的若干份,然后再计算元素宽度所占的份数。
例如 iPhone5
和 iPhone6
对应的设备宽度分别为 320px
和 375px
,现在将其分为100 列,那么每一列为 3.2px
和 3.75px
。不同的设备宽度对应的每一列的宽度都不一样。在设置元素的宽度时,以列为媒介即可。
同样一个 <div>
,设置它的宽度为10 份,那么在 iPhone5
中该 <div>
的宽就是 32px
,而在 iPhone6
中该 <div>
的宽度就是 37.5px
。通过这种方式就实现了不同宽度的设备中,一个元素的大小可以等比例的缩放。
但在我们日常开发中,从设计稿量出来都是像素,假设从设计稿量出来是 100px
,那怎么知道这是多少列呢?
此时就需要一个转换了,我们需要算出测量出来的宽度在总宽度中究竟占几列。
假如设计稿是按照 iPhone 6/7/8
尺寸(750px)设计的,我们测量出来的是 100px
,而换算成设备像素(375px)就是 50px
。得知一列的宽度和总宽度,那么可以算出 50/3.75
约等于13.33 列,也就是设置为13.33 * 1
列的宽度。
如下面例子所述:
iniiPhone6 375px iPhone6 Plus 414px 现在设计师按照 750px 起稿,有一个元素测量出来是 375px(物理像素) 设备宽度是375px,该元素换算成设备宽度: 375 / 2 = 187.5 接下来计算列数:187.5 / 3.75 = 50 设置该元素的宽度为:50 * 一列宽度 在iPhone6中:50 * 3.75px = 187.5px 在iPhone6 Plus中:50 * 4.14px = 207px
这个一列的宽度,实际上就是 rem
。也就是说 rem
的适配方式,是根据屏幕的宽度配合列数算出一列的宽度,之后设置 HTML
根元素的 font-size
就为一列的宽度即可。
下面这种方案是设计稿尺寸和列数之间的转换,只需要将量出来的尺寸小数点往前移动两位即可。
js
(function (doc, win, designWidth) {
const html = doc.documentElement; // 获取html根元素
function refreshRem() {
const width = html.clientWidth; // 获取CSS像素(设备宽度)
// 当设备宽度大于设计稿时,测量出来是多少就是多少
if(width >= designWidth) {
html.style.fontSize = '100px'
} else {
// 否则要算出每列宽度
// 如iPhone6,每一列的宽度为 375/750 = 0.5px,相当于分成750列
// 但是 ont-size 不可以设置为0.5px,所以要x100
// 那么每一列宽度就变成了50px,相当于分成7.5列
// 在px和rem转换时,设计稿的100px --> 设备上的50px --> 1rem
// 3.75rem --> 设计稿375px --> 设备显示187.5px
html.style.fontSize = 100 * (width / designWidth) + 'px';
}
}
doc.addEventListener('DOMContentLoaded', refreshRem);
})(document, window, 750);
vw、vh适配
CSS3
除了 rem,还有4个和 viewport 相关的单位,分别为 vw
、vh
、vmin
和 vmax
。
vw
的全称是 Viewport Width,1vw 相当于window.innerWidth
的 1%vh
的全称是 Viewport Height,1vh 相当于window.innerHeight
的 1%vmin
的值是当前vw
和vh
中较小的值vmax
的值是当前vw
和vh
中较大的值
vw
相当于直接将屏幕分为了 100 列,1vw
就是 1 列,那么按照 iPhone 6/7/8
设备宽度为 375px
,因此 1vw
就是 3.75px
,根据上面的公式:
js
html.style.fontSize = 100 * (width / designWidth) + 'px'
将根元素的字体大小设置为 50px
,和设计稿测量出来的尺寸转换会非常方便,只需小数点往前移动 2 位即可。
以 iPhone6/7/8
为基准,由于 1vw
相当于屏幕的一列,所对应的宽度为 3.75px
,所以可计算出 50px
对应的 vw
为 50 / 3.75 = 13.33333333vw
。
html
<div class="container"></div>
css
:root {
font-size: 13.3333333vw;
}
.container {
width: 3.75rem;
height: 3.75rem;
background-color: blue;
}
可以看到,使用 vw
的最大优点就是不需要使用 JavaScript 来做处理了,因为分列 vw
已经处理了,唯一需要做的就是计算根元素字体应设置多少 vw
,然后从设计稿测量出来的尺寸仍然是小数点前移 2 位即可。