CSS 提供了一些控制网页布局的几个重要工具,包括浮动、Flexbox 和定位等。这些工具没有优劣之分,只是实现布局的方式略有不同。
浮动布局
- 浮动元素会脱离标准流,在标准流中不占位置。
- 浮动元素比标准流高半个级别,可以覆盖标准流中的元素。
- 下一个浮动元素会在上一个浮动元素后面左右浮动。
- 浮动元素有特殊的显示效果(一行可以显示多个,可以设置宽高),即浮动后的元素具有行内块元素的特点,但是中间是没有缝隙的。
浮动的初衷
浮动能将一个元素(通常是一张图片)拉到其容器的一侧,这样文档流就能够包围它。浮动元素会被移出正常文档流,并被拉到容器边缘。文档流会重新排列,但是它会包围浮动元素此刻所占据的空间。如果让多个元素向同侧浮动,它们就会挨着排列。
上面所述,才是浮动涉及的初衷,尽管我们现在不总是这样使用它,浮动本身不是为了实现页面布局而设计的。在CSS早期,开发人员发现使用简单的浮动就可以移动页面的各个部分,从而实现各种各样的布局。
如今,不用浮动也能更好地实现布局,我们可以完全弃用浮动。但是如果要支持 IE 浏览器,现在放弃浮动还为时过早。只有 IE10 和 IE11 支持 Flexbox,而且还有一些 bug。如果不想碰到 bug,或者需要支持旧版浏览器,浮动也许是更好的选择。
- 如果旧代码用到了浮动布局,为了维护旧代码,我们需要了解浮动的工作原理
- 浮动布局通常不需要很多的标记,新的布局方法则需要添加额外的容器元素。如果修改样式时不允许修改标记,浮动更能满足要求
- 要实现将图片移动到网页一侧,并且让文字围绕图片的效果,浮动仍然是唯一的方法
看个列子,使用浮动来定位四个灰色盒子,并且在盒子里面将图片浮动到文字的一侧:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
/* 全局设置为border-box */
:root {
box-sizing: border-box;
}
*,
::before,
::after {
box-sizing: inherit;
}
body {
background-color: #eee;
font-family: Arial, Helvetica, sans-serif;
}
/* 用猫头鹰选择器设置全局外边距 */
body * + * {
margin-top: 1.5em;
}
.container {
max-width: 1080px;
margin: 0 auto;
}
header {
padding: 1em 1.5em;
color: #fff;
background-color: lightblue;
border-radius: .5em;
margin-bottom: 1.5em;
}
.main {
padding: 0 1.5em;
background-color: #fff;
border-radius: .5em;
}
.clearfix::before,
.clearfix::after {
display: table;
content: " ";
}
.clearfix::after {
clear: both;
}
.media {
float: left;
margin: 0 1.5em 1.5em 0;
width: calc(50% - 1.5em);
padding: 1.5em;
background-color: #eee;
border-radius: 0.5em;
}
.media:nth-child(odd) {
clear: left;
}
.media-image {
float: left;
margin-right: 1.5em;
}
.media-body {
overflow: auto;
margin-top: 0;
}
.media-body h4 {
margin-top: 0;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>唐诗三百首</h1>
</header>
<main class="main clearfix">
<h2>李白和杜甫</h2>
<div>
<div class="media">
<img class="media-image" src="img1.PNG" alt="">
<div class="media-body">
<h4>将进酒</h4>
<p>
君不见黄河之水天上来,奔流到海不复回。 君不见高堂明镜悲白发,朝如青丝暮成雪。 人生得意须尽欢,莫使金樽空对月。
</p>
</div>
</div>
<div class="media">
<img class="media-image" src="img2.PNG" alt="">
<div class="media-body">
<h4>静夜思</h4>
<p>
床前明月光,疑是地上霜。 举头望明月,低头思故乡。
</p>
</div>
</div>
<div class="media">
<img class="media-image" src="img3.PNG" alt="">
<div class="media-body">
<h4>春望</h4>
<p>
国破山河在,城春草木深。 感时花溅泪,恨别鸟惊心。 烽火连三月,家书抵万金。 白头搔更短,浑欲不胜簪。
</p>
</div>
</div>
<div class="media">
<img class="media-image" src="img4.PNG" alt="">
<div class="media-body">
<h4>登高</h4>
<p>
风急天高猿啸哀,渚清沙白鸟飞回。 无边落木萧萧下,不尽长江滚滚来。 万里悲秋常作客,百年多病独登台。 艰难苦恨繁霜鬓,潦倒新停浊酒杯。
</p>
</div>
</div>
</div>
</main>
</div>
</body>
</html>
这里涉及个概念,双容器模式:将内容放置到两个嵌套的容器中,然后给内层的容器设置外边距,让它在外层容器中居中。例子中,<body> 就是外层容器。因为它默认是 100% 的网页宽度,所以不用给它添加新的样式。在 <body> 内部,整个网页的内容放在了 <div class=“container” >,也就是内层容器中。对于内层容器,需要设置一个 max-width,并将外边距设置为 auto,使内容居中。
.container {
max-width: 1080px;
margin: 0 auto;
}
容器折叠和清除浮动
容器折叠
白色的背景区域,没有向下延伸到包含媒体盒子,为什么?
这是因为浮动元素不同于普通文档流的元素,它们的高度不会加到父元素上。这可能看起来很奇怪,但是恰好体现了浮动的设计初衷。浮动是为了实现文字围绕浮动元素排列的效果。在段落里浮动图片时,段落的高度并不会增长到能够容纳该图片。也就是说,如果图片比段落文字高,下一段会直接从上一段的文字下面开始,两段文字都会围绕浮动的图片排列。
在主元素里,除了页面标题,其他元素都设置了浮动,所以容器的高度只包含页面标题的高度,浮动的媒体元素则扩展到主元素的白色背景下面。这种行为并不是我们想要的,主元素应该向下扩展到包含灰色的盒子。
伪元素——一种特殊的选择器,可以选中文档的特定部分。伪元素以双冒号(::)开头,大部分浏览器为了向后兼容也支持单冒号的形式。最常见的伪元素是::before和::after,用来向元素的开始或者结束位置插入内容。
清除浮动
不用额外的div标签,我们可以用伪元素(pseudo-element)来实现。使用::after伪元素选择器,就可以快速地在DOM中在容器末尾添加一个元素,而不用在HTML里添加标记。
.clearfix::before, // 让 ::before 和 ::after 伪元素都显示出来
.clearfix::after {
display: table; // 防止伪元素的外边距折叠
content: " ";
}
.clearfix::after { // 只有 ::after 伪元素需要清除浮动
clear: both;
}
在清除浮动时使用display: table能够包含外边距。创建一个display: table元素,也就在元素内隐式创建了一个表格行和一个单元格。因为外边距无法通过单元格元素折叠,所以也无法通过设置了display: table的伪元素折叠。
浮动陷阱
浏览器会将浮动元素尽可能地放在靠上的地方。
因为盒子2比盒子1矮,所以它下面有多余的空间给盒子3。盒子3会“抓住”盒子1,而不是清除盒子1的浮动。因此盒子3不会浮动到最左边,而是浮动到盒子1的右下角。这种行为本质上取决于每个浮动块的高度。即使高度相差1px,也会导致这个问题。
要想修复这个问题很简单:清除第三个浮动元素上面的浮动。更通用的做法是,清除每行的第一个元素上面的浮动。
可以使用:nth-child() 伪类选择器选中这些目标元素
.media {
float: left;
margin: 0 1.5em 1.5em 0;
width: calc(50% - 1.5em);
padding: 1.5em;
background-color: #eee;
border-radius: 0.5em;
}
.media:nth-child(odd) {
clear: left;
}
弹性布局
Flexbox,全称弹性盒子布局(Flexible Box Layout),是一种新的布局方式。跟浮动布局相比,Flexbox 的可预测性更好,还能提供更精细的控制。它能解决困扰开发的垂直居中和等高列问题。
Flexbox 的原则
给元素添加 display: flex,该元素就变成了一个弹性容器。它的直接子元素变成了弹性子元素。弹性子元素默认是在同一行按照从左到右的顺序并排排列。弹性容器像块元素一样填满可用宽度,但是弹性子元素不一定填满其弹性容器的宽度。弹性子元素高度相等,该高度由它们的内容决定。
还可以用 display: inline-flex。它创建了一个弹性容器,行为类似于 inline-block 元素。它会跟其他行内元素一起流式排列,但不会自动增长到100%的宽度。内部的弹性子元素跟使用 display: flex 创建的 Flexbox 里的弹性子元素行为一样。在实际开发时,很少用到display: inline-flex。
子元素按照主轴线排列,主轴的方向为主起点(左)到主终点(右)。垂直于主轴的是副轴。方向从副起点(上)到副终点(下)。这些轴的方向可以改变。
示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flexbox Document</title>
<style>
:root {
box-sizing: border-box;
}
*,
::before,
::after {
box-sizing: inherit;
}
body {
background-color: #709b90;
font-family: Arial, Helvetica, sans-serif;
}
/* 全局外边距 */
body * + * {
margin-top: 1.5em;
}
/* 双容器,网页内容居中 */
.container {
max-width: 1080px;
margin: 0 auto;
}
/*指定为弹性容器,让元素成为弹性子元素 */
.site-nav {
display: flex;
padding: .5em;
list-style-type: none;
background-color: #5f4b44;
border-radius: .2em;
}
.site-nav > li {
margin-top: 0;
}
/* 让链接成为块级元素,这样就能撑开父元素的高度 */
.site-nav > li > a {
display: block;
background-color: #cc6b51;
padding: .5em 1em;
color: white;
text-decoration: none;
}
/* 选中所有前面有列表项的列表项(除了第一项之外的所有列表项) */
.site-nav > li + li {
margin-left: 1.5em;
}
/* 弹性盒子内的auto外边距会填充所有可用空间 */
.site-nav > .nav-right {
margin-left: auto;
}
.title {
padding: 1.5em;
background-color: #fff;
}
/* 主容器为Flexbox */
.flex {
display: flex;
}
.flex > * + * {
margin-top: 0;
margin-left: 1.5em;
}
/* 列宽度分配,第一列得到2/3 */
.column-main {
flex: 2;
}
/* 列宽度分配,第二列得到1/3 */
.column-sidebar {
flex: 1;
display: flex;
flex-direction: column;
}
.column-sidebar > .title {
flex: 1;
}
.login-form h3 {
margin: 0;
font-size: .9em;
font-weight: bold;
text-align: right;
text-transform: uppercase;
}
/* 给文本类型的输入框(不包含复选框和单选按钮)添加样式 */
.login-form input:not([type=checkbox]):not([type=radio]) {
display: block;
width: 100%;
margin-top: 0;
}
.login-form button {
margin-top: 1em;
border: 1px solid #cc6b5a;
background-color: white;
padding: .5em 1em;
cursor: pointer;
}
.centered {
text-align: center;
}
.centered small {
text-transform: uppercase;
}
/* 让弹性子元素在主轴和副轴方向上均居中 */
.cost {
display: flex;
justify-content: center;
align-items: center;
line-height: .7;
}
/* 覆盖猫头鹰选择器设置的外边距 */
.cost > span {
margin-top: 0;
}
.cost-currency {
font-size: 2em;
}
.cost-dollars {
font-size: 4em;
}
.cost-cents {
font-size: 1,5em;
align-self: flex-start; /* 覆盖这个子元素的align-items,将其与容器顶部而不是中间对齐 */
}
.cta-button {
display: block;
background-color: #cc6b5a;
color: white;
padding: .5em 1em;
text-decoration: none;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Ink</h1>
</header>
<nav>
<ul class="site-nav">
<li><a href="/">Home</a></li>
<li><a href="/features">Features</a></li>
<li><a href="/pricing">Pricing</a></li>
<li><a href="/support">Support</a></li>
<li class="nav-right"><a href="/about">About</a></li>
</ul>
</nav>
<main class="flex">
<div class="column-main title">
<h1>Team collaboration done right</h1>
<p>Thousands of teams from all over this world turn to <b>Ink</b> to communicate and get things done.</p>
<h1>Communication around the globe</h1>
<p>Thousands of teams from all over this world turn to <b>Ink</b> to communicate and get things done.</p>
<h1>Instant access to your team's documents</h1>
<p>Thousands of teams from all over this world turn to <b>Ink</b> to communicate and get things done.</p>
<h1>Intuitice interface</h1>
<p>Thousands of teams from all over this world turn to <b>Ink</b> to communicate and get things done.</p>
</div>
<div class="column-sidebar">
<div class="title">
<form class="login-form" action="">
<h3>Login</h3>
<p>
<label for="username">Username</label>
<input id="username" type="text" name="username">
</p>
<p>
<label for="password">Password</label>
<input id="password" type="password" name="password">
</p>
<button type="submit">Login</button>
</form>
</div>
<div class="title centered">
<small>Starting at</small>
<div class="cost">
<span class="cost-currency">$</span>
<span class="cost-dollars">20</span>
<span class="cost-cents">.00</span>
</div>
<a class="cta-button" href="/pricing">Sign up</a>
</div>
</div>
</main>
</div>
</body>
</html>
弹性容器的属性
flex-wrap属性
允许弹性子元素换到新的一行或多行显示。可以设置为nowrap(初始值)、wrap或者wrap-reverse。
启用换行后,子元素不再根据flex-shrink值收缩,任何超过弹性容器的子元素都会换行显示。flex-flow属性
flex-direction和flex-wrap的简写。例如,flex-flow:column wrap指定弹性子元素按照从上到下的方式排列,必要时换到新的一列。justify-content属性
当子元素未填满容器时,控制子元素沿主轴方向的间距。关键字:flex-start、flex-end、center、space-between以及space-around。
默认值flex-start让子元素从主轴的开始位置顺序排列,比如主轴方向为从左到右的话,开始位置就是左边。align-items属性
控制子元素在副轴方向的对齐方式。
align-items的初始值为stretch,在水平排列的情况下让所有子元素填充容器的高度,在垂直排列的情况下让子元素填充容器的宽度,因此它能实现等高列。align-content属性
如果开启了换行(用flex-wrap), align-content属性就可以控制弹性容器内沿副轴方向每行之间的间距。它支持的值有flex-start、flex-end、center、stretch(初始值)、space-between以及space-around。这些值对间距的处理类似上面的justify-content。
弹性子元素的属性
align-self属性
控制弹性子元素沿着容器副轴方向的对齐方式。跟弹性容器的align-items属性效果相同,但是它能单独给弹性子元素设定不同的对齐方式。auto为初始值,会以容器的align-items值为准。其他值会覆盖容器的设置。
align-self属性支持的关键字与align-items一样:flex-start、flex-end、center、stretch以及baseline。order属性
order属性能改变子元素排列的顺序。还可以将其指定为任意正负整数。如果多个弹性子元素有一样的值,它们就会按照源码顺序出现。
谨慎使用order,让屏幕上的视觉布局顺序和源码顺序差别太大会影响网站的可访问性。