前端响应式布局方案
简要介绍
前端开发中,静态网页通常需要适应不同分辨率的设备,常用的自适应解决方案包括媒体查询、百分比、rem和vw/vh等。
媒体查询
使用@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结合媒体查询实现响应式布局外,也可以通过百分比单位 " % " 来实现响应式的效果。
比如当浏览器的宽度或者高度发生变化时,通过百分比单位,通过百分比单位可以使得浏览器中的组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果。
百分比相对于直接父元素,height百分比相对于height,width百分比相对于width。
百分比单位的缺点
- 计算困难,如果我们要定义一个元素的宽度和高度,按照设计稿,必须换算成百分比单位。
- 各个属性中如果使用百分比,相对父元素的属性并不是唯一的。比如width和height相对于父元素的width和height,而margin、padding不管垂直还是水平方向都相对比父元素的宽度、border-radius则是相对于元素自身等等,造成我们使用百分比单位容易使布局问题变得复杂。
自适应场景下rem解决方案(不推荐)
rem单位
rem是一个灵活的、可扩展的单位,由浏览器转化像素并显示。与em单位不同,rem单位无论嵌套层级如何,都只相对于浏览器的根元素(HTML元素)的font-size。默认情况下,html元素的font-size为16px,所以:
1 rem = 12px
通过rem来实现响应式布局
rem单位都是相对于根元素html的font-size来决定大小的,根元素的font-size相当于提供了一个基准,当页面的size发生变化时,只需要改变font-size的值,那么以rem为固定单位的元素的大小也会发生响应的变化。 因此,如果通过rem来实现响应式的布局,只需要根据视图容器的大小,动态的改变font-size即可.
function refreshRem() {
var docEl = doc.documentElement;
var width = docEl.getBoundingClientRect().width;
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
flexible.rem = win.rem = rem;
}
win.addEventListener('resize', refreshRem);
上述代码中将视图容器分为10份,font-size用十分之一的宽度来表示,最后在header标签中执行这段代码,就可以动态定义font-size的大小,从而1rem在不同的视觉容器中表示不同的大小,用rem固定单位可以实现不同容器内布局的自适应。
rem 布局的缺点
通过rem单位,可以实现响应式的布局,特别是引入相应的postcss相关插件,免去了设计稿中的px到rem的计算。rem单位在国外的一些网站也有使用,这里所说的rem来实现布局的缺点,或者说是小缺陷是:
在响应式布局中,必须通过js来动态控制根元素font-size的大小。
也就是说css样式和js代码有一定的耦合性。且必须将改变font-size的代码放在css样式之前。
通过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都等于视图宽度的百分之一。
postcss-px-to-viewport
- 将px单位转换为视口单位的 (vw, vh, vmin, vmax) 的 PostCSS 插件
- github地址: https://github.com/evrone/postcss-px-to-viewport
- 如果你的样式需要做根据视口大小来调整宽度,这个脚本可以将你CSS中的px单位转化为vw,1vw等于1/100视口宽度。
输入
.class {
margin: -10px .5vh;
padding: 5vmin 9.5px 1px;
border: 3px solid black;
border-bottom-width: 1px;
font-size: 14px;
line-height: 20px;
}
.class2 {
padding-top: 10px; /* px-to-viewport-ignore */
/* px-to-viewport-ignore-next */
padding-bottom: 10px;
/* Any other comment */
border: 1px solid black;
margin-bottom: 1px;
font-size: 20px;
line-height: 30px;
}
@media (min-width: 750px) {
.class3 {
font-size: 16px;
line-height: 22px;
}
}
输出
.class {
margin: -3.125vw .5vh;
padding: 5vmin 2.96875vw 1px;
border: 0.9375vw solid black;
border-bottom-width: 1px;
font-size: 4.375vw;
line-height: 6.25vw;
}
.class2 {
padding-top: 10px;
padding-bottom: 10px;
/* Any other comment */
border: 1px solid black;
margin-bottom: 1px;
font-size: 6.25vw;
line-height: 9.375vw;
}
@media (min-width: 750px) {
.class3 {
font-size: 16px;
line-height: 22px;
}
}
安装
npm install postcss-px-to-viewport --save-dev
配置参数
{
unitToConvert: 'px',
viewportWidth: 320,
unitPrecision: 5,
propList: ['*'],
viewportUnit: 'vw',
fontViewportUnit: 'vw',
selectorBlackList: [],
minPixelValue: 1,
mediaQuery: false,
replace: true,
exclude: undefined,
include: undefined,
landscape: false,
landscapeUnit: 'vw',
landscapeWidth: 568
}
在项目根目录创建 postcss.config.js文件
module.exports = {
plugins: {
'postcss-px-to-viewport': {
unitToConvert: 'px', // 需要转换的单位,默认为"px"
viewportWidth: 1920, // 设计稿的视口宽度
unitPrecision: 5, // 单位转换后保留的精度
propList: ['*'], // 能转化为vw的属性列表
viewportUnit: 'vw', // 希望使用的视口单位
fontViewportUnit: 'vw', // 字体使用的视口单位
selectorBlackList: [], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
mediaQuery: false, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: undefined, // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
include: undefined, // 如果设置了include,那将只有匹配到的文件才会被转换
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1920, // 横屏时使用的视口宽度
},
},
}
unitToConvert
(String) 需要转换的单位,默认为"px"viewportWidth
(Number) 设计稿的视口宽度unitPrecision
(Number) 单位转换后保留的精度propList
(Array) 能转化为vw的属性列表- 传入特定的CSS属性;
- 可以传入通配符""去匹配所有属性,例如:[''];
- 在属性的前或后添加"*",可以匹配特定的属性. (例如['position'] 会匹配 background-position-y)
- 在特定属性前加 "!",将不转换该属性的单位 . 例如: ['*', '!letter-spacing'],将不转换letter-spacing
- "!" 和 ""可以组合使用, 例如: ['', '!font*'],将不转换font-size以及font-weight等属性
viewportUnit
(String) 希望使用的视口单位fontViewportUnit
(String) 字体使用的视口单位selectorBlackList
(Array) 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。- 如果传入的值为字符串的话,只要选择器中含有传入值就会被匹配
- 例如
selectorBlackList
为['body']
的话, 那么.body-class
就会被忽略
- 例如
- 如果传入的值为正则表达式的话,那么就会依据CSS选择器是否匹配该正则
- 例如
selectorBlackList
为[/^body$/]
, 那么body
会被忽略,而.body
不会
- 例如
- 如果传入的值为字符串的话,只要选择器中含有传入值就会被匹配
minPixelValue
(Number) 设置最小的转换数值,如果为1的话,只有大于1的值会被转换mediaQuery
(Boolean) 媒体查询里的单位是否需要转换单位replace
(Boolean) 是否直接更换属性值,而不添加备用属性exclude
(Array or Regexp) 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件- 如果值是一个正则表达式,那么匹配这个正则的文件会被忽略
- 如果传入的值是一个数组,那么数组里的值必须为正则
include
(Array or Regexp) 如果设置了include
,那将只有匹配到的文件才会被转换,例如只转换 'src/mobile' 下的文件
(include: /\/src\/mobile\//
)- 如果值是一个正则表达式,将包含匹配的文件,否则将排除该文件
- 如果传入的值是一个数组,那么数组里的值必须为正则
landscape
(Boolean) 是否添加根据landscapeWidth
生成的媒体查询条件@media (orientation: landscape)
landscapeUnit
(String) 横屏时使用的单位landscapeWidth
(Number) 横屏时使用的视口宽度
exclude
和include
是可以一起设置的,将取两者规则的交集。