rem适配和vw适配

什么是适配?

适配:在不同尺寸的手机设备上,页面相对性的达到合理的展示(自适应)或者保持统一效果的等比缩放(看起来差不多)

适配的方法:

1
2
3
4
5
6
7
8
9
1、百分比适配

2、viewport缩放

3、DPR缩放

4、rem适配

5、vw、vh适配

百分比适配

width:宽度的百分比是相对于父盒子width内容宽的比。没有父盒子就是相对于浏览器的宽。
height:高度的百分比是相对于父盒子height内容高的比。
padding、margin:padding和margin不管任何方向百分比都是相对于父盒子width内容宽的比。
border:不能书写百分数

子盒子如果绝对定位,width百分比参考的是距离最近,且有定位的父盒子的width(算上padding.);
height百分比参考的是距离最近,且有定位的父盒子的height(算上padding.);
padding,margin百分比参考的是距离最近,且有定位的父盒子的width(算上padding.);

viewport缩放适配

把所有机型的设备独立像素(CSS像素)设置成一致的。

比如说,一张设计图是按照iPhone6设计的,iPhone6的分辨率是750x1334,其设备独立像素(CSS像素)就是375x667,那么如果项目网页只想在iPhone6这种分辨率的设备里使用的话,我们代码中像素直接按照设计稿那样给定像素大小就行了。但是这样子的页面放到了其他分辨率、其他设备独立像素(CSS像素)的设备中去的时候,页面就乱套了,比如iPhone6 plus的分辨率是828x1472(设备独立像素(CSS像素)是414x736)。

这时候,如果能将所有机型的设备独立像素(CSS像素)都设置为一致的,比如全设置为iPhone6的375x667,然后代码样式按照375x667的设计稿去给大小,这样网页在所有机型设备中都能做到适配。

1、viewport需要通过js动态的设置(不能直接把device的值设置成数值)

2、通过设置比例(初始比例以及缩放比例),把宽度缩放成一致的

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

上面代码中的initial-scale就是缩放比,缩放比=css像素/viewport视口宽度。

为了把所有机型的设备独立像素(CSS像素)设置成一致的,就需要设置 initial-scale 的大小。若我们想把所有机型的CSS像素都设置为iPhone6的标准(375x667),通过公式:缩放比=css像素宽度/viewport视口宽度,我们已知viewport视口宽度375,只要获取到当前机型的CSS像素宽度就可以知道缩放比 initial-scale 到底要设置为多少了。而当前机型的CSS像素宽度实际上就是html标签的宽度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,minimum-scale=1,maximum-scale=1" id="view">
<title></title>
<style>
body{
margin: 0;
}
.box div{
/* width: 25%; */
width: 93.75px;
height: 100px;
float: left;
}
.box div:nth-child(1){
background: red;
}
.box div:nth-child(2){
background: green;
}
.box div:nth-child(3){
background: blue;
}
.box div:nth-child(4){
background: yellow;
}
</style>
<script>
// 注意这里的script标签必须放在<head>标签里面

(function(){
//获取css像素(viewport没有缩放)
var curWidth=document.documentElement.clientWidth;
var curWidth=window.innerWidth;
var curWidth=window.screen.width;// 最好用这个

console.log(curWidth);

var targetWidth=375;
var scale=curWidth/targetWidth;
console.log(scale);

var view=document.getElementById('view');
console.log(view.content);

view.content='initial-scale='+scale+',user-scalable=no,minimum-scale='+scale+',maximum-scale='+scale+'';
})();
</script>
</head>

若实现没有写<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">

则采用如下代码👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function(){
/*
*iphone 6plus
*原来的尺寸 414
*要变成的尺寸(以iPhone 6为基准)375 375
*比例 414/375
*/
var curWidth = window.screen.width; //iphone 6plus为414px
var targetWidth = 375;
var scale = curWidth/targetWidth;
var meta = document.createElement("meta");
meta.name = "viewport";
meta.content = 'initial-scale='+scale+',minimum-scale='+scale+',maximum-scale='+scale+'';
document.head.appendChild(meta);
})()

缺点:

  1. 就像在viewport设置宽度的时候,可以把宽度设置成一个固定值一样,会出现所有的设备看上去都是同样的大小,没有分别了,不太好,厂商特意做出各种大小的手机,还要弄成一样,那人家买大屏机有什么意义
  2. 算出的的值在一些有小数的情况下可能会出现误差

DPR缩放适配

根据dpr的值,把视口进行缩放,缩放到物理像素,也就是把css像素的值设置成物理像素,让所有的设备都变成一个css像素对应一个设备像素。比如iPhone6,设备像素是750x1334,设备独立像素(CSS像素)是375x667,我们就把视口进行缩放,让设备独立像素也变成750x1334,从而一个css像素对应一个设备像素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function(){
var meta=document.querySelector('meta[name="viewport"]');
var scale=1/window.devicePixelRatio;

if(!meta){
//这个条件成立说明用户没有写meta标签,我需要创建一个
meta=document.createElement('meta');
meta.name='viewport';
meta.content='width=device-width,initial-scale='+scale+',user-scalable=no,minimum-scale='+scale+',maximum-scale='+scale+'';
document.head.appendChild(meta);
}else{
meta.setAttribute('content','width=device-width,initial-scale='+scale+',user-scalable=no,minimum-scale='+scale+',maximum-scale='+scale+'');
}
})();

缺点:

  • 宽度还是需要用百分比,感觉没什么卵用。

rem适配

讲rem之前先来回顾一下em。

em 作为font-size的单位时,其代表父元素的字体大小,作为其他属性单位时,代表自身字体大小

1、chrom下有最小字体限制,必需为12px,所以这个值不能小于12

2、如果两个一样的元素,但是里面字体不一样,那就不能统一设置了。或者元素字体变化了,就又要统一设置一遍

1
2
3
4
5
6
7
8
9
10
.em{
font-size: 30px;

border: 1px solid #000;
width: 10em; /* 1em=30px 作为其他属性单位时,代表自身字体大小 */
height: 10em;
}
.em p{
font-size: 2em; /* 1em=30px 作为font-size的单位时,其代表父元素的字体大小*/
}

rem CSS3新增的一个相对单位,是相对于根元素的字体大小,即html的字体大小;

1
2
3
4
5
6
7
8
9
html{
font-size: 20px;
}
.rem{
width: 10rem; /* 1rem=20px; */
height: 10rem;

border: 1px solid #000;
}

rem适配的原理:把所有的设备的宽度都分成相同的若干列,再计算元素宽度所占的份数

rem适配,如果可以根据不同机型的屏幕宽度(设备独立像素CSS像素)确定根节点html的字体大小(即一列的宽度),而1rem就等于html节点的字体大小(1rem即为一列的宽),进而在构造页面的时候可以把设计稿中的宽度 / 1rem所代表的px的值,从而得出具体多少rem(即元素宽度所占的份数)从而达到适配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*
1、元素适配的宽度(算出来的)
=元素所占的列数*一列的宽
=元素所占的列数*1rem

2、元素在设计稿里的宽度(量出来的)
3、列数(随便给的)
100
4、一列的宽度(算出来的)
=屏幕实际的宽度(css像素)/列数
一列的宽度就是1rem

5、元素实际占的列数(算出来的)
=元素设计稿里的宽/一列的宽
=元素设计稿里的宽/1rem
*/

/*
var colWidth=屏幕实际的宽度(css像素)/100;
50*colWidth //50列所占的宽
*/

var colWidth=0; //一列的宽
var col=100; //列数

//分别算出iphone5与iphone6里面一列的宽度
colWidth=375/100; //3.75px iphone6
colWidth=320/100; //3.2px iphone5

//假如一个div需要占10列,算出div的分别在两个手机里的宽度
var divWidth=0; //div的宽度
divWidth=10*3.75; //37.5px iphone6里div的宽度
divWidth=10*3.2; //32px iphone5里div的宽度

//根据设计稿里元素的宽算出来它所占的列数
var divWidth=50; //div在设计稿里实际的宽(量出来的)
var divCol=0; //要算出div所占的列数

//以iphone6为例,一列的宽为3.75px,那50px占多少列?
divCol=50/3.75; //13.333 在iphone6里所占的列数
divCol=50/3.2; //15.625 在iphone5里所占的列数
1
2
3
4
5
6
7
8
//width: 10rem;	实际切图时候给的值
//元素适配的宽度=元素所占的列数*一列的宽
//元素适配的宽度 => width
//元素所占的列数 => 10
//一列的宽 => rem
/* html{
font-size:屏幕实际的宽度(css像素)/列数,即一列的宽度
} */

综上可知,rem适配的关键是确定根节点html的字体大小,即所谓的一列的宽度。有两种方式可以确定html的font-sitze,一种通过JS设置,另一种通过媒体查询进行设置。

JS设置根节点font-size(简易版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* 
1、元素适配的宽度=元素所占的列数*1rem
2、一列的宽度=屏幕实际的宽度(css像素)/列数
3、元素实际占的列数=元素设计稿里的宽/1rem
*/
(function(){
var html=document.documentElement; //html
var width=html.clientWidth; //css像素

console.log(width);

html.style.fontSize=width/16+'px'; //把屏幕分成了16列,以iphone为例得出一个列的值为整数
})();


//iphone5
//1rem=20px; 一列的宽度
//80/1rem=80/20=4; 元素实际占的列数
//4*1rem=4rem; 元素适配的宽度


//iphone6
//1rem=375/16=23.4375; 一列的宽度
//4*1rem=4*23.4375=93.75; 元素适配的宽度
//4*93.75=375; css的宽度

这里再多说一嘴关于切图的事,一般设计稿都是按照设备像素大小进行设计的(为了保证图片高清),切图得到的像素大小要先处以设备的DPR,再进行rem的换算。比如IPhone6的设计稿量出来一个div的宽是200px,则代码中给的px是200/DPR=200/2=100px,否则过大,然后再用这个100px进行rem的换算。

1
2
3
4
//真正切图时候的方法!!!
//1、算rem,还是根据设备实际的设备独立像素css像素算
//2、量出一个元素在设计稿里的尺寸
//3、拿这个尺寸除以DPR值后,再去换算rem

JS设置根节点font-size(最优版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
<title></title>
<style>
body{
margin: 0;
}

div{
background: green;
float: left;
/*
187.5px
*/
width: 1.875rem;
height: 1.875rem;
}
img{
width: 7.5rem;
}
</style>
<script>
/**
* 立即执行函数,确定根节点font-size的大小,这里以iPhone6的750宽度进行设置,
* 最终html标签的font-size为50px,由于(clientWidth / designWidth)已经是0.5,所以
* 在切图量出来的像素大小不必再除以DPR了,量出187.5px直接可以写1.875rem
* @param {Object} document
* @param {Object} window
* @param {number=} designWidth 设计稿中设备的物理像素宽度
*/
(function (doc, win, designWidth) {
var html = doc.documentElement;

function refreshRem() {
var clientWidth = html.clientWidth;

if (clientWidth >= designWidth) {//给宽度一个最大值,如果设备的宽度已经超过设计稿的尺寸了,统一按一个值去算(传的第三个参数)
html.style.fontSize = '100px';
} else {
html.style.fontSize = 100 * (clientWidth / designWidth) + 'px';
}
};

doc.addEventListener('DOMContentLoaded', refreshRem);
})(document, window, 750);

</script>
</head>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<img src="images/img_12.jpg" alt="">
</body>
</html>

这个方案有个比较爽的地方就是设计稿量出来多少,直接小数点前移两位即转化为rem的值。举个例子,有个div设计稿量出来宽度是187.5px,那么在代码中就应该为187.5/DPR=187.5/2=93.75px,而html的font-size为50px,也就是1rem=50px,那么93.75px=93.75/50=1.875rem,又绕回来了,直接小数点前移两位就得到rem的值。非常方便,免去了手动除以DPR的步骤。

媒体查询置根节点font-size

利用CSS3媒体查询来手动设置根节点的font-size,苏宁在用,不过我觉得有点low,不方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<style>
html {
font-size: 50px
}

body {
font-size: 24px
}

@media screen and (min-width: 320px) {
html {
font-size:21.33px
}

body {
font-size: 12px
}
}

@media screen and (min-width: 360px) {
html {
font-size:24px
}

body {
font-size: 12px
}
}

@media screen and (min-width: 375px) {
html {
font-size:25px
}

body {
font-size: 12px
}
}

@media screen and (min-width: 384px) {
html {
font-size:25.6px
}

body {
font-size: 14px
}
}

@media screen and (min-width: 400px) {
html {
font-size:26.67px
}

body {
font-size: 14px
}
}

@media screen and (min-width: 414px) {
html {
font-size:27.6px
}

body {
font-size: 14px
}
}

@media screen and (min-width: 424px) {
html {
font-size:28.27px
}

body {
font-size: 14px
}
}

@media screen and (min-width: 480px) {
html {
font-size:32px
}

body {
font-size: 15.36px
}
}

@media screen and (min-width: 540px) {
html {
font-size:36px
}

body {
font-size: 17.28px
}
}

@media screen and (min-width: 720px) {
html {
font-size:48px
}

body {
font-size: 23.04px
}
}

@media screen and (min-width: 750px) {
html {
font-size:50px
}

body {
font-size: 24px
}
}
</style>

hotcss适配

作为一个移动端的项目,必须做好移动端适配,这里使用的方案是hotcss,其原理和rem适配原理是一样的,只不过hotcss.js做得更加精细。下面以vue项目举例。

下载好hotcss文件夹,里面有hotcss.js和px2rem.scss两个文件。hotcss.js在main.js中引入。

另外将样式重置文件common.css放在assets文件夹中,然后在App.vue中引入。

px2rem.scss中:

1
2
3
4
5
@function px2rem($px){
@return $px*320/$designWidth/20 + rem;
}
// 设置设计图的宽度
$designWidth : 750;

项目设计图的宽度就是750px。px2rem.scss就是一个函数,用于将px转为合适的rem,设计稿量出一个div宽度为200px,则不必再考虑转化为多少的rem,直接使用width: px2rem(200)即可。

【补充】sass引入公共文件

对于hotcss中的px2rem.scss文件,每个 .vue 文件都要手动引用它,这样很麻烦,所以我们需要只引用一次px2rem.scss就可以在所有的 .vue 文件中使用,那么要怎么做到呢?

看一下Vue Cli3的官网文档:文档一文档二

在项目根路径下新建文件 vue.config.js:

1
2
3
4
5
6
7
8
9
10
11
// vue.config.js
module.exports = {
css: {
loaderOptions: {
// 给 sass-loader 传递选项
sass: {
data: `@import "./src/lib/hotcss/px2rem.scss";` //注意这个分号不能漏掉
},
}
}
};

配置完成之后,就可以保证所有的页面都可以使用这个px2rem.css而无需每个页面都手动引入。

每次修改了 vue.config.js 的时候,都要重新启动项目。

这里我踩了一个坑,在配置sass的时候漏了分号

vw、vh适配

vw:Viewport’s width的简写,1vw等于视口宽度的1%
vh:Viewport’s height的简写,1vh等于视口高度的1%
vmin:取vw和vh中最小的值
vmax: 取vw和vh中最大的值
vmin 和 vmax 一般很少用到。

ios 8之前和Android 4.4之前的版本都不支持vw、vh适配,这也是其之前不火的原因,现在设备基本都是很新的系统版本了,除非要考虑很旧的版本的兼容,可以放心使用vw适配。

1vw浏览器会自动视为视口宽度的1%,其实就和我们手动将视口宽度(设备的宽度)都100列一个道理。

vw、vh适配一般有两种用法,分别是直接转为vw单位进行使用,另一种是通过vw设置根节点字体大小,页面里的尺寸依然使用rem。

直接使用vw作为像素单位

依旧以iPhone(750x1334)为例子,有:

1
2
3
4
5
6
7
8
iphone6
1vw=375/100=3.75px;

设计稿(750px规格)中要四个盒子平铺一行,则一个div的宽度为187.5px,那么css代码给的px就应该为93.75px,
93.75px/3.75px=25vw
这时候只要给每个div宽度设为25vw就可以实现四个盒子平铺一行,而且做到所有机型都适配

(iphone6 plus的话则 1vw=414/100=4.14px; 其他机型同理)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
body {
margin: 0 auto;
}

div {
float: left;
/* width: 25%; */
/* width: 93.75px; */
width: 25vw;
background-color: red;
}
</style>

</head>

<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</body>
</html>

这样需要每次都手动去换算px转vm,这个过程很麻烦,我们可以模仿hotcss来通过一个函数来替我们换算,我采用scss预处理器来实现这个函数。

现在VSCode中安装两个插件Sass和EasySass,安装完成之后,每当我新建一个scss文件修改并保存之后都会自动生成对应的css文件。

新建index.scss文件:

1
2
3
4
5
6
7
8
9
10
11
@function vw($px){
@return $px / 750 * 100vw;
}
body{
margin: 0 ;
}
div{
width: vw(187.5);
background-color: red;
float: left;
}

关键代码就是👇

1
2
3
4
5
6
7
/**
* $px是从设计稿量出的大小
* 这个750是默认设计稿是按照750px的物理像素设计的,若是640px的设计稿就换为640即可,其他同理
*/
@function vw($px){
@return $px / 750 * 100vw;
}

这样,我们直接可以将从750px的设计稿中量出来的宽度187.5放入这个vw函数中,实现类似hotcss那样的便捷的使用。。生成的index.css内容如下:

1
2
3
4
5
6
7
8
9
body {
margin: 0 auto;
}

div {
width: 25vw;
background-color: red;
float: left;
}

其自动转为了合适的vm。因此随后的所有的宽度都可以使用vm()来进行适配。

vw设置根节点字体大小,其他使用rem

相比于前者,这种vw适配方案更加流行。

在iPhone6(750x1334)为基准的rem适配里,html的fontsize的值设为了50px,具体参见上文中的【JS设置根节点font-size(最优版)】,然后设计稿中量出来多少px,就将小数点前移两位即为对应的多少rem。那么我们可以将50px换为vw,在iPhone6中1vw = 375/100=3.75vw,则50px = 50/3.75=13.333333333333334vw。我们直接将html的fontsize设置为13.333333333333334vw,然后就可以做到所有机型都适配了。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="zh-CN">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
html {
font-size: 13.333333333333334vw;
}

body {
margin: 0 ;
}

div {
float: left;
width: 1.875rem;
background-color: red;
}
</style>
<script>
</script>
</head>

<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</body>

</html>

相比之下省去了一大段设置html的fontsize大小的JS逻辑代码,更加便捷。

存在的问题

字体默认大小过大和图片错位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<style>
html {
font-size: 13.333333333333334vw;
}
body {
margin: 0;
padding: 0;
}
div {
width: 2rem;
height: 2rem;
background-color: red;
}
</style>


<body>
<div>
<div>你好</div>
</div>
</body>

这时因为html的fontsize设置了大小,导致默认字体大小变大。同时这一设置还会造成图片错位的现象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<style>
html {
font-size: 13.333333333333334vw;
}

body {
margin: 0;
padding: 0;
}

div {
width: 2rem;
height: 2rem;
background-color: red;
}

div img {
width: 0.5rem;
height: 0.5rem;
}
</style>
</head>

<body>
<div>
<img src="https://sponsor-static.segmentfault.com/169d32a59c16fd2f9aeed64f1face530.jpg" alt="">
</div>
</body>

如上图所示,我并没有给img设置任何padding、margin或者定位等位置信息,它就是发生了错位。这时因为图片默认相对字体对齐,而我们在html中设置了字体大小是一个会非常大的值,所以图片也因此发生了错位。

解决办法就是在body中设置默认的字体大小为12px;这一就不会有上述的情况发生了。