盒模型
1. 元素宽度问题
问题:当想要让两个元素并排显示,宽度占满一行,可以设置 float: left
均向左浮动,宽度设置 width: 50%
,理想情况下可以符合要求,但如果某一个元素设置了内边距,则不会并排,此时两个元素实际宽度加起来已经超过了 100%
宽度。
原因:当给一个元素设置宽高时,设置的是内容的宽高,所有内边距、边框、外边距都会追加到该宽度上。
1.1 避免魔术数值
解决上述问题最简单的方法就是减少一个元素的宽度,比如将第二个元素宽度设为 46%
,但这种方式并不可靠,46%
是一个魔术数值,不是一个理想的值,而是通过样式试验出来的。
编程中不推荐魔术数值,因为往往难以解释一个魔术数值生效的原因。
一个替代魔术数值的方式是让浏览器帮忙计算,使用 calc()
函数,如 whidth: calc(30% - .5em)
。
1.2 调整盒模型
- 在 CSS 中可以使用
box-sizing
属性调整盒模型行为。 box-sizing
默认值为content-box
,任何指定的宽高都只会设置内容盒子的大小。- 设置为
border-box
后,宽高会成为内容、内边距、边框的大小总和。
1.3 全局设置 border-box
- 建议在项目之初全局设置
border-box
,能省去很多不必要的麻烦,但为了不影响第三方的样式,必要时需要给第三方组件的顶级容器恢复成content-box
。 - 盒模型通常不会被继承,需要通过
inherit
强制继承。
::root {
/* 根元素设置 */
box-sizing: border-box;
}
*,
::before,
::after {
/* 其它所有元素继承 */
box-sizing: inherit;
}
/* 将第三方恢复,避免影响样式 */
.third-party-component {
box-sizing: content-box;
}
1.4 给列之间加上间隔
使用 border-box
之后,如果想给列之间加上间隔,可以给某一元素加上外边距,再调整元素的宽度:
.mian {
float: left;
width: 70%;
background-color: #fff;
border-radius: .5em;
}
.sidebar {
box-sizing: border-box;
float: left;
/* 减去外部留白 */
width: 29%;
margin-left: 1%;
padding: 1.5em;
background-color: #fff;
border-radius: .5em;
}
如果外边距与宽度单位不一致,可以使用 calc()
计算宽度。
2. 元素高度问题
- 通常需要避免给元素指定明确的高度。普通文档流是为有限的宽度和无限的高度设计的。
- 容器的高度最好由容器内容决定,而不是容器自己决定。
普通文档流是指网页元素的默认布局行为。行内元素跟随文字的方向从左到由排列,块元素独占一行,前后有换行。
2.1 控制溢出行为
当明确设置一个元素的高度,内容可能会溢出容器。
用 overflow
属性可以控制溢出内容的行为,可以设置下面 4 个值:
visible
:默认值,所有内容可见,即使溢出容器边缘。hidden
:溢出容器内边距边缘的内容被裁剪,不可见。scroll
:容器出现滚动条,通过滚动查看剩余内容。auto
:只有内容溢出时才会出现滚动条。
建议使用 auto
,大多数情况下,不希望滚动条一直出现。
2.2 百分比高度的备选方案
要让百分比高度生效,需要保证父元素有明确的高度。
2.2.1 等高列
为了让两个元素并列等高,可以设定确定的高度,但高度值太大了就会在容器底部留下大片空白,太小了就会溢出。最好是让元素自己决定高度,并让更矮的元素拓展高度与更高的元素相同。 通过 CSS 表格布局和 Flexbox 可以实现这种效果。
2.2.2 CSS 表格布局
给容器设置 display: table
,给两列元素设置 display: table-cell
即可实现等高列。
.container {
display: table;
/* 明确指定宽度 */
width: 100%;
/* 设置单元格间距使等高列分隔 */
border-spacing: 1.5em 0;
}
.main {
display: table-cell;
box-sizing: border-box;
width: 70%;
background-color: #fff;
border-radius: .5em;
}
.sidebar {
display: table-cell;
box-sizing: border-box;
width: 30%;
/* 外边距不会生效 */
margin-left: 1.5em;
padding: 1.5em;
background-color: #fff;
border-radius: .5em;
}
注意
- 默认情况下,显示为
table
的元素宽度不会拓展到100%
,因此需要明确指定宽度。 - 外边距不会作用于
table-cell
元素。
为了让等高列有间距,可以使用表格元素的 border-spacing
属性设置单元格间距。该属性接受两个长度值:水平间距和垂直间距。但这样会产生一个副作用:该属性也会作用于表格的外边缘。
如果想消除对等高列左右外边距的影响,可以使用负外边距,这需要给整个表格包裹一层新容器:
.wrapper {
margin: 0 -1.5em;
}
提示
- 标准文档流中,未设置宽度时,块级元素的水平
margin
会通过盒模型间接改变内容区域的宽度。如果值为正会压缩宽度,负值会拉伸。
2.2.3 flexbox
给容器设置 display: flex
就会变成弹性容器,子元素默认等高,因此很容易实现等高列。
.container {
display: flex;
width: 100%;
}
.main {
box-sizing: border-box;
width: 70%;
background-color: #fff;
border-radius: .5em;
}
.sidebar {
box-sizing: border-box;
width: 30%;
margin-left: 1.5em;
padding: 1.5em;
background-color: #fff;
border-radius: .5em;
}
警告
除非别无选择,否则不要明确设置元素高度。
2.3 使用 min-height 和 max-height
可以使用 min-height
和 max-height
指定最小高度或最大高度,而不是明确值,这样元素可以在范围内自动决定高度。
类似的属性还有 min-width
和 max-width
。
2.4 垂直居中内容
为什么 vertical-align
声明不生效?
vertical-align
声明只会影响行内元素或者table-cell
元素。- 对于行内元素来说,它控制该元素跟同一行内其它元素的对其关系。比如,可以用它控制一个行内的图片跟相邻文字对齐。
- 对于显示为
table-cell
的元素,它控制了内容在单元格内的对齐。如果页面用了 CSS 表格布局,那可以用vertical-align
实现垂直居中。
.container {
height: 20vh;
display: table-cell;
vertical-align: middle;
}
CSS 中最简单实现垂直居中的方法就是给容器相等的上下内边距,让容器和内容自行决定高度。不管容器中内容是行内元素,块元素或是其它形式,这种方式都有效。
.container {
padding: 2em 0;
}
垂直居中指南:
- 可以用一个自然高度的容器吗? 给容器加上相等的上下内边距,让内容居中。
- 容器需要指定高度或避免使用内边距吗? 对容器使用
display: table-cell
和vertical-align: middle
。 - 可以用 Flexbox 吗? 如果可以则用
flex
布局居中。 - 容器内的内容只有一行文字吗? 设置一个大的行高,让它等于理想的容器高度,容器如果自动高度则会拓展到能够容纳行高。
- 容器和内容高度都知道吗? 绝对定位,只有当前面方法都无效时才推荐这种方法。
- 不知道内部元素高度? 用绝对定位结合变形(transform)。
3. 负外边距
- 不同于内边距和边框宽度,外边距可以设置负值。
- 对于设置了宽度的块元素,如果左边或顶部设置负外边距,元素就会向左或向上移动,右边或底部设置,则自身不会移动,而会把后面的元素拉过来。
- 对于没设置宽度的块元素,左右负外边距会把元素向左向右拉伸,顶部设置则元素上移动,底部设置则把下面的元素拉过来。
4. 外边距折叠
4.1 文字折叠
外边距折叠的主要原因与包含文字的块之间的间隔有关。
段落默认有 1em
的上外边距和下外边距,但前后叠放两个段落时,外边距不会相加,而会折叠只产生 1em
间距。
注意
折叠外边距的大小等于相邻外边距中的最大值。
4.2 多个外边距折叠
- 即使两个元素不是相邻的兄弟节点也会产生外边距折叠,比如父子元素的顶部外边距就会折叠。
- 在没有其它 CSS 的影响下,所有相邻的顶部和底部外边距都会折叠。
<main>
<h2>Come join us!</h2>
<div>
<p>hello world!</p>
</div>
</main>
上述代码中,<h2>
底部外边距,<div>
和 <p>
顶部的外边距会折叠,最终 <h2>
和 <p>
间隔是三者中最大值。
说明
只有上下外边距会产生折叠,左右外边距不会。
4.3 容器外部折叠
如果不想让外边距发生折叠,可以采取下面的方法:
- 容器和内容之间:对容器使用
overflow: auto
或非visible
的值,防止内部元素的外边距跟容器外部的外边距折叠。这种方式副作用最小。 - 容器和内容之间:在两个外边距之间加上边框或者内边距,防止折叠。
- 容器和内容之间:如果容器为浮动元素、内联块、绝对定位或固定定位时,外边距不会在它外面折叠。
- 容器和内容或内部元素之间:当使用 Flexbox 布局时,弹性布局内的元素之间不会发生外边距折叠。网格布局同理。
- 当元素显示为
table-cell
时不具备外边距属性,因此不会折叠。此外还有table-row
和大部分其它表格显示类型,但不包括table
、table-inline
、table-caption
。
5. 容器内的元素间距
容器内边距和内容的外边距相互作用:
<aside class="sidebar">
<a href="/twitter" class="button-link">follow us on Twitter</a>
<a href="/facebook" class="button-link">like us on Facebook</a>
</aside>
.sidebar {
width: 30%;
padding: 1.5em;
box-sizing: border-box;
background-color: #fff;
border-radius: .5em;
}
.button-link {
display: block;
padding: 0.5em;
color: #fff;
background-color: #0090C9;
text-align: center;
text-decoration: none;
text-transform: uppercase;
}
此时要给两个按钮增加间距,但不能给第一个加上边距,否则加上容器的内边距,会间隔很大不协调,可以选中第二个链接加上顶部边距:
/* 选中链接的兄弟元素 */
.button-link + .button-link {
margin-top: 1.5em;
}
如果后面有其他不同元素,仍需单独加上间距,在某些情况下有更好的处理方式。
5.1 更通用的解决方案:猫头鹰选择器
猫头鹰选择器:* + *
,可以选中所有元素后面跟着的兄弟元素,即有着相同父级的非第一个子元素。
/* 放在body后面,就只会选中body内的元素 */
body * + * {
margin-top: 1.5em;
}
通过猫头鹰选择器,就不需要单独针对元素设置间距,但在某些不想加外边距的地方需要覆盖。
通常只在有并列元素或多列布局时这样使用。
6. 总结
- 总是全局设置
border-box
,以便得到预期的元素大小。 - 避免明确设置元素高度,以免出现溢出问题。
- 使用现代布局技术,如 Flexbox 或
display: table
实现等高列或垂直居中。 - 如果外边距行为奇怪,就增加措施预防外边距折叠。
- 某些情况使用猫头鹰选择器,能带来很大便捷。