前言
随着互联网的快速发展,移动端进入热门,以往的 PC 端页面已经不再适合移动端手机设备,然而前端开发中,静态网页通常需要适应不同分辨率的设备,常用的自适应解决方案包括媒体查询、百分比、rem 和 vw/vh 等, 本文我们从 px 单位 开始介绍,分析了解 px 在移动端布局中的不足,并且介绍了几种不同的自适应解决方案
- px和视口
- 媒体查询
- 百分比
- rem
- vw/vh
一、px和视口
在静态网页中,我们经常用像素(px)作为单位,来描述一个元素的宽高以及定位信息。在pc端,通常认为css中,1px所表示的真实长度是固定的。
那么,px 真的是一个设备无关,跟长度单位米和分米一样是固定大小的吗?
我们先来猜想一下,如果是一样的话,我们在pc 端写一行字,字体大小设置 16px 那么在移动端字体大小肯定也是一样,而实际我们可以看下面两张图片分别表示pc端下和移动端下的显示结果,在网页中我们设置的font-size统一为 16px,现在你还认为 px 像素大小还跟设备无关吗?
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> --><title>Document</title><style>* {padding: 0;margin: 0;list-style: noe;}div {font-size: 16px;}</style>
</head><body><div>Hello world!!</div>
</body></html>
从上面两幅图的对比可以看出,字体都是16px,显然在pc端中文字正常显示,而在移动端文字很小,几乎看不到,这就说明在css中1px并不是固定大小,直观从我们发现在移动端1px所表示的长度较小,所以导致文字显示不清楚
那么css中的1px的真实长度到底由什么决定呢?
要知道 css 真实长度首先我们首先介绍像素和视口的概念
像素
像素是网页布局的基础,一个像素表示了计算机屏幕所能显示的最小区域,像素分为两种类型:css像素和物理像素
我们在js或者css代码中使用的px单位就是指的是css像素,物理像素也称设备像素,只与设备或者说硬件有关,同样尺寸的屏幕,设备的密度越高,物理像素也就越多。下表表示css像素和物理像素的具体区别:
- css像素 为web开发者提供,在css中使用的一个抽象单位(相对单位)
- 物理像素 只与设备的硬件密度有关,任何设备的物理像素都是固定的
通过上面介绍相信都了解了css像素和物理像素,那么css像素与物理像素的转换关系是怎么样的呢?为了明确css像素和物理像素的转换关系,我们必须还要先了解视口是什么
视口
广义的视口,是指浏览器显示内容的屏幕区域,狭义的视口包括了布局视口、视觉视口和理想视口(完美视口)
布局视口(layout viewport)
布局视口定义了pc网页在移动端的默认布局行为,因为通常pc的分辨率较大,布局视口默认为980px。也就是说在不设置网页的viewport的情况下,pc端的网页默认会以布局视口为基准,在移动端进行展示。因此我们可以明显看出来,默认为布局视口时,pc端的网页在移动端展示很模糊
视觉视口(visual viewport)
视觉视口表示浏览器内看到的网站的显示区域,用户可以通过缩放来查看网页的显示内容,从而改变视觉视口。视觉视口的定义,就像拿着一个放大镜分别从不同距离观察同一个物体,视觉视口仅仅类似于放大镜中显示的内容,因此视觉视口不会影响布局视口的宽度和高度
理想视口(完美视口)(ideal viewport)
理想视口或者应该全称为“理想的布局视口”,在移动设备中就是指设备的分辨率。换句话说,理想视口或者说分辨率就是给定设备物理像素的情况下,最佳的“布局视口”。
上面视口介绍中,最重要的是要明确理想视口的概念,在移动端中,理想视口或者说分辨率跟物理像素之间有什么关系呢?
分辨率:
为了理清分辨率和物理像素之间的联系,我们介绍一个用 DPR(Device pixel ratio)设备像素比来表示,则可以写成:
1 DPR = 物理像素/分辨率
在不缩放的情况下,一个css像素就对应一个dpr,也就是说,在不缩放
1 CSS像素 = 物理像素/分辨率
在移动端的布局中,我们可以通过 viewport 元标签来控制布局,比如一般情况下,我们可以通过下述标签使得移动端在理想视口下布局:
<meta id="viewport" name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1; user-scalable=no;">
上述 meta 标签的每个属性的详细介绍如下
属性名 | 取值 | 描述 |
---|---|---|
width | 正整数 | 定义布局视口的宽度,单位为像素 |
height | 正整数 | 定义布局视口的高度,单位为像素,很少使用 |
initial-scale | [0,10] | 初始缩放比例,1表示不缩放 |
minimum-scale | [0,10] | 最小缩放比例 |
maximum-scale | [0,10] | 最大缩放比例 |
user-scalable | yes/no | 是否允许手动缩放页面,默认值为yes |
其中我们来看width属性,在移动端布局时,在meta标签中我们会将width设置称为device-width,device-width一般是表示分辨率的宽,通过width=device-width的设置我们就将布局视口设置成了理想的视口
px与自适应
我们了解到了当通过viewport元标签,设置布局视口为理想视口时,1个css像素可以表示成:
1 CSS像素 = 物理像素/分辨率
我们知道在pc端的布局视口通常情况下为980px,移动端以iphone6为例,分辨率为375 * 667,也就是说布局视口在理想的情况下为375px。比如现在我们有一个750px * 1134px的视觉稿,那么在pc端,一个css像素可以如下计算:
PC端: 1 CSS像素 = 物理像素/分辨率 = 750 / 980 = 0.76 px
而在 iphone6下:
iphone6:1 CSS像素 = 物理像素 /分辨率 = 750 / 375 = 2 px
也就是说在PC端,一个CSS像素可以用0.76个物理像素来表示,而iphone6中 一个CSS像素表示了2个物理像素。此外不同的移动设备分辨率不同,也就是1个CSS像素可以表示的物理像素是不同的,因此如果在css中仅仅通过px作为长度和宽度的单位,造成的结果就是无法通过一套样式,实现各端的自适应
二、媒体查询
通过前面讲述,不同端的设备下,在css文件中,1px所表示的物理像素的大小是不同的,因此通过一套样式,是无法实现各端的自适应。由此我们联想
如果一套样式不行,那么能否给每一种设备各一套不同的样式来实现自适应的效果?
学过css3想必大家都知道结果了,使用@media媒体查询可以针对不同的媒体类型定义不同的样式,特别是响应式页面,可以针对不同屏幕的大小,编写多套样式,从而达到自适应的效果
@media screen and (max-width: 960px){body{background-color:#FF6699}
}@media screen and (max-width: 768px){body{background-color:#00FF66;}
}@media screen and (max-width: 550px){body{background-color:#6633FF;}
}@media screen and (max-width: 320px){body{background-color:#FFFF00;}
}
上述的代码通过媒体查询定义了几套样式,通过max-width设置样式生效时的最大分辨率,上述的代码分别对分辨率在0~320px,320px~550px,550px~768px以及768px~960px的屏幕设置了不同的背景颜色
通过媒体查询,可以通过给不同分辨率的设备编写不同的样式来实现响应式的布局,比如我们为不同分辨率的屏幕,设置不同的背景图片。比如给小屏幕手机设置@2x图,为大屏幕手机设置@3x图,通过媒体查询就能很方便的实现
但是媒体查询的缺点也很明显,如果在浏览器大小改变时,需要改变的样式太多,那么多套样式代码会很繁琐
三、百分比
通过上述描述,我们可以用px结合媒体查询实现响应式布局外,我们也可以通过百分比单位 " % " 来实现响应式的效果
比如当浏览器的宽度或者高度发生变化时,通过百分比单位,通过百分比单位可以使得浏览器中的组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果
要实现百分比布局我们就要先了解 css中的子元素中的百分比(%)到底是谁的百分比?
最直接的理解,我们会认为子元素的百分比完全相对于直接父元素,height
百分比相对于height,width
百分比相对于width
,当然这种理解是正确的,但是根据css的盒式模型,除了height、width
属性外,还具有padding、border、margin
等等属性。那么这些属性设置成百分比,是根据父元素的那些属性呢?此外还有 border-radius
和 translate
等属性中的百分比,又是相对于什么呢?下面来具体分析
百分比的具体分析
子元素
height
和width
的百分比
子元素的height或width中使用百分比,是相对于子元素的直接父元素,width相对于父元素的width、height
相对于父元素的height。比如
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> --><title>Document</title><style>* {padding: 0;margin: 0;list-style: none;}#parent {width: 300px;height: 300px;background-color: aquamarine;}#parent .child {width: 50%;height: 50%;background-color: bisque;}</style>
</head><body><div id="parent"><div class="child">子元素</div></div>
</body>
top
和bottom
、left
和right
子元素的 top
和 bottom
如果设置百分比,则相对于直接非static定位(默认定位)的父元素的高度,同样子元素的 left
和 right
如果设置百分比,则相对于直接非static定位(默认定位的)父元素的宽度
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> --><title>Document</title><style>* {padding: 0;margin: 0;list-style: none;}#parent {position: relative;width: 300px;height: 300px;background-color: burlywood;}.box {width: 150px;height: 150px;background-color: coral;}.child {position: absolute;font-size: 16px;width: 100px;height: 100px;left: 50%;top: 50%;background-color: rgb(207, 39, 223);}</style>
</head><body><div id="parent"><div class="box"></div><div class="child"></div></div>
</body>
padding
和margin
子元素的 padding
如果设置百分比,不论是垂直方向或者是水平方向,都相对于直接父亲元素的 width
,而与父元素的 height
无关, margin
跟 padding
一样,margin也是如此,子元素的 margin
如果设置成百分比,不论是垂直方向还是水平方向,都相对于直接父元素的 width
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> --><title>Document</title><style>* {padding: 0;margin: 0;list-style: none;}#parent {position: relative;width: 200px;height: 300px;background-color: burlywood;border: 1px solid #eee;}.child {font-size: 16px;width: 0px;height: 0px;margin-left: 50%;margin-top: 50%;padding-top: 50%;padding-left: 50%;background-color: rgb(207, 39, 223);}.box {position: absolute;width: 100px;height: 100px;background-color: crimson;}</style>
</head><body><div id="parent"><div class="box"></div><div class="child"></div></div>
</body>
子元素的初始宽高为 0,通过 padding
可以将父元素撑大,上图的紫色部分是一个正方形,且边长为100px,说明padding
不论宽高,如果设置成百分比都相对于父元素的 width
border-radius
border-radius不一样,如果设置 border-radius
为百分比,则是相对于自身的宽度
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> --><title>Document</title><style>* {padding: 0;margin: 0;list-style: none;}#parent {width: 200px;height: 300px;background-color: burlywood;border: 1px solid #eee;}.child {font-size: 16px;width: 100px;height: 100px;border-radius: 50%;background-color: rgb(207, 39, 223);}</style>
</head>
<body><div id="parent"><div class="child"></div></div>
</body>
除了border-radius外,还有比如translate、background-size等都是相对于自身的
百分比单位布局应用
百分比单位在布局上应用还是很广泛的,我们介绍一种应用
比如我们要实现一个固定长宽比的长方形,比如要实现一个长宽比为 4:3
的长方形,我们可以根据 padding
属性来实现,因为 padding
不管是垂直方向还是水平方向,百分比单位都相对于父元素的宽度,因此我们可以设置 padding-top
为百分比来实现,长宽自适应的长方形,这样就设置了一个长宽恒定比例的长方形
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> --><title>Document</title><style>* {padding: 0;margin: 0;list-style: none;}#parent {width: 100%;background-color: burlywood;border: 1px solid #eee;padding-top: 75%;}</style>
</head><body><div id="parent"></div>
</body>
百分比单位缺点
通过上述对于百分比单位的介绍我们很容易看出如果全部使用百分比单位来实现响应式的布局,有明显的以下两个缺点:
- 计算困难,如果我们要定义一个元素的宽度和高度,按照设计稿,必须换算成百分比单位
- 各个属性中如果使用百分比,相对父元素的属性并不是唯一的。比如
width
和height
相对于父元素的width
和height
,而margin、padding
不管垂直还是水平方向都相对比父元素的宽度、border-radius
则是相对于元素自身等等,造成我们使用百分比单位容易使布局问题变得复杂
四、rem 自适应
rem单位
首先我们要知道什么是rem
单位。rem
是一个灵活的、可扩展的单位,由浏览器转化像素并显示。与em单位不同,rem单位无论嵌套层级如何,都只相对于浏览器的根元素(HTML元素)的 font-size
默认谷歌情况下,html 元素的 font-size
为16px,(但是也可以设置) 所以:
1 rem = 16px
为了计算方便,通常可以讲 html 的 font-size 设置成
html{ font-size: 62.5% }
这种情况下:
1 rem = 10px
通过rem来实现响应式布局
rem单位都是相对于根元素html的font-size
来决定大小的,根元素的font-size
相当于提供了一个基准,当页面的size发生变化时,只需要改变font-size
的值,那么以rem为固定单位的元素的大小也会发生响应的变化。
因此,如果通过rem来实现响应式的布局,只需要根据视图容器的大小,动态的改变font-size即可
// 方法一
function refreshRem() {var docEl = document.documentElement;var width = docEl.getBoundingClientRect().width;var rem = width / 10;docEl.style.fontSize = rem + 'px';
}
window.addEventListener('resize', refreshRem);// 方法二
!(function () {var styleNode = document.createElement("style");var w = document.documentElement.clientWidth / 10; // 适配比例styleNode.innerHTML = `html{font-size: ${w}px!important }`; // 设置样式document.head.appendChild(styleNode); // 插入到头部})();
上述代码中将视图容器分为10份,font-size用十分之一的宽度来表示,最后在header标签中执行这段代码,就可以动态定义font-size的大小,从而1rem在不同的视觉容器中表示不同的大小,用rem固定单位可以实现不同容器内布局的自适应
rem2px和px2rem
如果在响应式布局中使用rem单位,那么存在一个单位换算的问题,rem2px表示从rem换算成px,这个就不说了,只要rem乘以相应的font-size中的大小,就能换算成px。更多的应用是px2rem,表示的是从px转化为rem
比如给定的视觉稿为750px(物理像素),如果我们要将所有的布局单位都用rem来表示,一种比较笨的办法就是对所有的 height
和 width
等元素,乘以相应的比例,现将视觉稿换算成rem单位,然后一个个的用rem来表示。另一种比较方便的解决方法就是,在css中我们还是用px来表示元素的大小,最后编写完css代码之后,将css文件中的所有px单位,转化成rem单位
px2rem
的原理也很简单,重点在于预处理以px为单位的css文件,处理后将所有的px变成rem单位。可以通过两种方式来实现
webpack loader的形式
npm install px2rem-loader
module.exports = {// ...module: {rules: [{test: /\.css$/,use: [{loader: 'style-loader'}, {loader: 'css-loader'}, {loader: 'px2rem-loader',// options hereoptions: {remUni: 75,remPrecision: 8}}]}]}
}
webpack中使用postcss plugin
npm install postcss-loader
var px2rem = require('postcss-px2rem');
module.exports = {module: {loaders: [{test: /\.css$/,loader: "style-loader!css-loader!postcss-loader"}]},postcss: function() {return [px2rem({remUnit: 75})];}
}
使用预处理语义 less 或 scss 进行转化
@rem: (750/10rem); // 750 为设计稿的宽度 10 为铺满移动端的最大值.head {position: absolute;width: (750/@rem);height: 100px;background-color: bisque;}
rem 布局应用举例
rem 布局的缺点
通过rem单位,可以实现响应式的布局,特别是引入相应的postcss相关插件,免去了设计稿中的px到rem的计算。rem单位在国外的一些网站也有使用,这里所说的rem来实现布局的缺点,或者说是小缺陷是
在响应式布局中,必须通过js来动态控制根元素font-size的大小
也就是说css样式和js代码有一定的耦合性。且必须将改变font-size的代码放在css样式之前
五、vw/vh 自适应
什么是vw/vh
css3中引入了一个新的单位vw/vh,与视图窗口有关,vw表示相对于视图窗口的宽度,vh表示相对于视图窗口高度,除了vw和vh外,还有vmin和vmax两个相关的单位。各个单位具体的含义如下
单位 | 含义 |
---|---|
vw | 相对于视窗的宽度,视窗宽度是100vw |
vh | 相对于视窗的高度,视窗高度是100vh |
vmin | vw和vh中的较小值 |
vmax | vw和vh中的较大值 |
这里我们发现视窗宽高都是100vw/100vh,那么vw或者vh,下简称vw,很类似百分比单位。vw和%的区别为
单位 | 含义 |
---|---|
% | 大部分相对于祖先元素,也有相对于自身的情况比如(border-radius、translate等) |
vw/vh | 相对于视窗的尺寸 |
从对比中我们可以发现,vw单位与百分比类似,单确有区别,前面我们介绍了百分比单位的换算困难,这里的 vw 更像 “理想的百分比单位”,任意层级元素,在使用vw单位的情况下,1vw都等于视图宽度的百分之一
vw单位换算
如果要将px换算成vw单位,很简单,只要确定视图的窗口大小(布局视口),如果我们将布局视口设置成分辨率大小,比如对于iphone6/7 375*667的分辨率,那么px可以通过如下方式换算成vw
1px = (1/375)*100 vw
此外,也可以通过postcss的相应插件,预处理css做一个自动的转换,postcss-px-to-viewport可以自动将px转化成vw、postcss-px-to-viewport的默认参数为:
var defaults = {viewportWidth: 320,viewportHeight: 568, unitPrecision: 5,viewportUnit: 'vw',selectorBlackList: [],minPixelValue: 1,mediaQuery: false
};
通过指定视窗的宽度和高度,以及换算精度,就能将px转化成vw
vw/vh 兼容性
可以在https://caniuse.com/ 查看各个版本的浏览器对vw单位的支持性, 绝大多数的浏览器支持vw单位,但是ie9-11不支持vmin和vmax,考虑到vmin和vmax单位不常用,vw单位在绝大部分高版本浏览器内的支持性很好,但是opera浏览器整体不支持vw单位,如果需要兼容opera浏览器的布局,不推荐使用vw
总结
移动端适配方案有很多,通过上述的方案可以知道每个方案有自己的优点也有自己的缺点,不过目前移动端适配方案主流还是使用 rem
或者 vw/vh
,实际开发我们还是根据实际情况确定适配方案