元素宽度的问题
盒模型的默认行为,当给一个元素设置宽或高的时候,指定的是内容的宽或高,所有内边距、边框、外边距都是追加到该宽度上的。
如果这些值使用不同的单位,情况就会更复杂。如下,使用浮动布局,将main和sidebar向左浮动,分别设置70%和30%的宽度。
::: demo [vanilla]
<html>
<body>
<div class="demo0">
<header>
<h1>Franklin Running Club</h1>
</header>
<div class="container">
<main class="main">
<h2>Come join us!</h2>
<p>
The Franklin Running Club meets at 6:00pm every Thursday at the town square.Runs are three to five miles, at your own pace.
</p>
</main>
<aside class="siderbar">
<div class="widget"></div>
<div class="widget"></div>
</aside>
</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo0 h2::before {
all: initial !important;
}
.demo0 {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.demo0 header {
color: #fff;
background-color: #0072b0;
border-radius: .5em;
}
.demo0 main {
display: block;
background-color: lightgreen;
border-radius: .5em;
}
.demo0 .siderbar {
padding: 1.5em;
background-color: lightgray;
border-radius: .5em;
}
/* 浮动布局 */
.demo0 .main {
float: left;
width: 70%;
}
.demo0 .siderbar {
float: left;
width: 30%;
}
</style>
:::
避免魔术数值
最笨的方法是减少其中一列(比如侧边栏)的宽度。可以将侧边栏改为宽26%,两列能够并排放下,但是这种方式不可靠。
魔术数值不是一个理想的值,而是通过改样式试出来的值。在编程中不推荐魔术数值,因为往往难以解释一个魔术数值生效的原因。如果不理解这个数值是怎么来的,就不会知道在不同的情况下会产生什么样的结果。
替代魔术数值的一个方法是让浏览器帮忙计算。如 calc(30% - 3em)
,但是还有更好的解决办法。
::: demo [vanilla]
<html>
<body>
<div class="demo1">
<header>
<h1>Franklin Running Club</h1>
</header>
<div class="container">
<main class="main">
<h2>Come join us!</h2>
<p>
The Franklin Running Club meets at 6:00pm every Thursday at the town square.Runs are three to five miles, at your own pace.
</p>
</main>
<aside class="siderbar">
<div class="widget"></div>
<div class="widget"></div>
</aside>
</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo1 h2::before {
all: initial !important;
}
.demo1 {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.demo1 header {
color: #fff;
background-color: #0072b0;
border-radius: .5em;
}
.demo1 main {
display: block;
background-color: lightgreen;
border-radius: .5em;
}
.demo1 .siderbar {
padding: 1.5em;
background-color: lightgray;
border-radius: .5em;
}
/* 浮动布局 */
.demo1 .main {
float: left;
width: 70%;
}
.demo1 .siderbar {
float: left;
width: calc(30% - 3em);
}
</style>
:::
调整盒模型
在 CSS 中可以使用 box-sizing 属性调整盒模型的行为。
box-sizing 的默认值为 content-box
,这意味任何指定的宽或高都只会设置内容盒子的大小。将 box-sizing 设置为 border-box
后,height 和 width 属性会设置内容、内边距以及边框的大小总和,这刚好符合示例的要求。
::: demo [vanilla]
<html>
<body>
<div class="demo2">
<header>
<h1>Franklin Running Club</h1>
</header>
<div class="container">
<main class="main">
<h2>Come join us!</h2>
<p>
The Franklin Running Club meets at 6:00pm every Thursday at the town square.Runs are three to five miles, at your own pace.
</p>
</main>
<aside class="siderbar">
<div class="widget"></div>
<div class="widget"></div>
</aside>
</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo2 h2::before {
all: initial !important;
}
.demo2 {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.demo2 header {
color: #fff;
background-color: #0072b0;
border-radius: .5em;
}
.demo2 main {
display: block;
background-color: lightgreen;
border-radius: .5em;
}
.demo2 .siderbar {
padding: 1.5em;
background-color: lightgray;
border-radius: .5em;
}
/* 浮动布局 */
.demo2 .main {
box-sizing: border-box;
float: left;
width: 70%;
}
.demo2 .siderbar {
box-sizing: border-box;
float: left;
width: 30%;
}
</style>
:::
全局设置 border-box
简单粗暴的做法
*,
::before,
::after {
box-sizing: border-box;
}
但是,如果在网页中使用了带样式的第三方组件,就可能会因此破坏其中一些组件的布局,尤其是当第三方组件在开发 CSS 的过程中没有考虑到使用者会修改盒模型时。最终需要写另外的样式将组件内的元素恢复为 content-box
。
有一种简单点的方式,是利用继承改一下修改盒模型的方式。
/* 根元素设置为 border-box */
:root {
box-sizing: border-box;
}
/* 其他元素和伪元素继承 */
*,
::before,
::after {
box-sizing: inherit;
}
盒模型通常不会被继承,但是使用 inherit
关键字可以强制继承。可以在必要时选中第三方组件的顶级容器,将其恢复为content-box
。这样组件的内部元素会继承该盒模型。
.third-party-component {
box-sizing: content-box;
}
给列之间加上间隔
建议用 em 指定间距,因为 em 单位的一致性更好。
::: demo [vanilla]
<html>
<body>
<div class="demo3">
<header>
<h1>Franklin Running Club</h1>
</header>
<div class="container">
<main class="main">
<h2>Come join us!</h2>
<p>
The Franklin Running Club meets at 6:00pm every Thursday at the town square.Runs are three to five miles, at your own pace.
</p>
</main>
<aside class="siderbar">
<div class="widget"></div>
<div class="widget"></div>
</aside>
</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo3 h2::before {
all: initial !important;
}
.demo3 {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.demo3 header {
color: #fff;
background-color: #0072b0;
border-radius: .5em;
}
.demo3 main {
display: block;
background-color: lightgreen;
border-radius: .5em;
}
.demo3 .siderbar {
padding: 1.5em;
background-color: lightgray;
border-radius: .5em;
}
/* 浮动布局 */
.demo3 .main {
box-sizing: border-box;
float: left;
width: 70%;
}
.demo3 .siderbar {
box-sizing: border-box;
float: left;
width: calc(30% - 1.5em);
margin-left: 1.5em;
}
</style>
:::
元素的高度问题
普通文档流是为限定的宽度和无限的高度设计的。内容会填满视口的宽度,然后在必要的时候折行。因此,容器的高度由内容天然地决定,而不是容器自己决定。
::: tip
普通文档流——指的是网页元素的默认布局行为。行内元素跟随文字的方向从左到右排列,当到达容器边缘时会换行。块级元素会占据完整的一行,前后都有换行。
:::
::: warning
之前对 border-box 的修改依然适用于高度,而且很有用,但是通常最好避免给元素指定明确的高度。
:::
控制溢出行为
当明确设置一个元素的高度时,内容可能会溢出容器。当内容在限定区域放不下,渲染到父元素外面时,就会发生这种现象。
用 overflow 属性可以控制溢出内容的行为,支持:
visible
(默认值)——所有内容可见,即使溢出容器边缘hidden
——溢出容器内边距边缘的内容被裁剪,无法看见scroll
——容器出现滚动条,用户可以通过滚动查看剩余内容。在一些操作系统上,会出现水平和垂直两种滚动条,即使所有内容都可见(不溢出)。不过,在这种情况下,滚动条不可滚动(置灰)。auto
——只有内容溢出时容器才会出现滚动条
通常情况下,建议使用 auto
而不是 scroll
,避免滚动条一直出现。
::: demo [vanilla]
<html>
<body>
<div class="demo4">
<div class="item-visible">visible,CSS入门容易,但精通不易。学习CSS并不是学习一两个小技巧,而是要理解这门语言的方方面面,并知道如何将其搭配使用。</div>
<div class="item-hidden">hidden,CSS入门容易,但精通不易。学习CSS并不是学习一两个小技巧,而是要理解这门语言的方方面面,并知道如何将其搭配使用。</div>
<div class="item-scroll">scroll,CSS入门容易,但精通不易。学习CSS并不是学习一两个小技巧,而是要理解这门语言的方方面面,并知道如何将其搭配使用。</div>
<div class="item-auto">auto,CSS入门容易,但精通不易。学习CSS并不是学习一两个小技巧,而是要理解这门语言的方方面面,并知道如何将其搭配使用。</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo4 div {
display: inline-block;
height: 150px;
width: 100px;
}
.demo4 .item-visible {
overflow: visible;
}
.demo4 .item-hidden {
overflow: hidden;
}
.demo4 .item-scroll {
overflow: scroll;
}
.demo4 .item-auto {
overflow: auto;
}
</style>
:::
百分比高度的备选方案
用百分比指定高度存在问题。百分比参考的是元素容器块的大小,但是容器的高度通常是由子元素的高度决定的。这样会造成死循环,浏览器处理不了,因此它会忽略这个声明。要想让百分比高度生效,必须给父元素明确定义一个高度。
使用百分比高度是想让一个容器填满屏幕。不过更好的方式是用视口的相对单位 vh,100vh 等于视口的高度。
等高列
CSS 表格布局
::: demo [vanilla]
<html>
<body>
<div class="demo5">
<header>
<h1>Franklin Running Club</h1>
</header>
<div class="container">
<main class="main">
<h2>Come join us!</h2>
<p>
The Franklin Running Club meets at 6:00pm every Thursday at the town square.Runs are three to five miles, at your own pace.
</p>
</main>
<aside class="siderbar">
<div class="widget"></div>
<div class="widget"></div>
</aside>
</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo5 h2::before {
all: initial !important;
}
.demo5 {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.demo5 header {
color: #fff;
background-color: #0072b0;
border-radius: .5em;
}
.demo5 .container {
display: table;
width: 100%;
}
.demo5 main {
display: table-cell;
width: 70%;
background-color: lightgreen;
border-radius: .5em;
}
.demo5 .siderbar {
display: table-cell;
width: 30%;
padding: 1.5em;
background-color: lightgray;
border-radius: .5em;
}
</style>
:::
使用 border-spacing
增加间隔,并采用负的 margin
修复对齐
::: demo [vanilla]
<html>
<body>
<div class="demo6">
<header>
<h1>Franklin Running Club</h1>
</header>
<div class="wrapper">
<div class="container">
<main class="main">
<h2>Come join us!</h2>
<p>
The Franklin Running Club meets at 6:00pm every Thursday at the town square.Runs are three to five miles, at your own pace.
</p>
</main>
<aside class="siderbar">
<div class="widget"></div>
<div class="widget"></div>
</aside>
</div>
</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo6 h2::before {
all: initial !important;
}
.demo6 {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.demo6 header {
color: #fff;
background-color: #0072b0;
border-radius: .5em;
}
.demo6 .wrapper {
margin-left: -1.5em;
margin-right: -1.5em;
}
.demo6 .container {
display: table;
width: 100%;
border-spacing: 1.5em 0;
}
.demo6 main {
display: table-cell;
width: 70%;
background-color: lightgreen;
border-radius: .5em;
}
.demo6 .siderbar {
display: table-cell;
width: 30%;
padding: 1.5em;
background-color: lightgray;
border-radius: .5em;
}
</style>
:::
Flexbox
::: demo [vanilla]
<html>
<body>
<div class="demo7">
<header>
<h1>Franklin Running Club</h1>
</header>
<div class="container">
<main class="main">
<h2>Come join us!</h2>
<p>
The Franklin Running Club meets at 6:00pm every Thursday at the town square.Runs are three to five miles, at your own pace.
</p>
</main>
<aside class="siderbar">
<div class="widget"></div>
<div class="widget"></div>
</aside>
</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo7 h2::before {
all: initial !important;
}
.demo7 {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.demo7 header {
color: #fff;
background-color: #0072b0;
border-radius: .5em;
}
.demo7 .container {
display: flex;
width: 100%;
}
.demo7 main {
width: 70%;
background-color: lightgreen;
border-radius: .5em;
}
.demo7 .siderbar {
width: 30%;
padding: 1.5em;
margin-left: 1.5em;
background-color: lightgray;
border-radius: .5em;
}
</style>
:::
::: warning
除非别无选择,否则不要明确设置元素的高度。先寻找一个替代方案。设置高度一定会导致更复杂的情况。
:::
使用 min-height 和 max-height
用这两个属性指定最小或最大值,而不是明确定义高度,这样元素就可以在这些界限内自动决定高度。
::: demo [vanilla]
<html>
<body>
<div class="demo8">
<div class="item-normal">normal; CSS入门容易,但精通不易。</div>
<div class="item-minheight">min-height: 5em; CSS入门容易,但精通不易。</div>
<div class="item-minheight">min-height: 5em; CSS入门容易,但精通不易。学习CSS并不是学习一两个小技巧,而是要理解这门语言的方方面面,并知道如何将其搭配使用。</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo8 div {
float: left;
width: 120px;
border: 1px solid lightgray;
}
.demo8 .item-minheight {
overflow: visible;
min-height: 5em;
}
</style>
:::
垂直居中内容
为什么vertical-align不生效
vertical-align 声明只会影响行内元素或者 table-cell 元素。
对于行内元素,它控制着该元素跟同一行内其他元素之间的对齐关系。比如,可以用它控制一个行内的图片跟相邻的文字对齐。
对于显示为 table-cell 的元素,vertical-align 控制了内容在单元格内的对齐。如果你的页面用了 CSS 表格布局,那么可以用 vertical-align 来实现垂直居中。
一个不好的做法就是,给容器设定一个高度值,然后试图让动态大小的内部元素居中。在实现这种效果时,请尽量交给浏览器来决定高度。
CSS 中最简单的垂直居中方法是给容器相等的上下内边距,让容器和内容自行决定自己的高度。
::: demo [vanilla]
<html>
<body>
<div class="demo9">
<header>
<h1>Franklin Running Club</h1>
</header>
</div>
</body>
</html>
<script>
</script>
<style>
.demo9 {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.demo9 header {
padding-top: 4em;
padding-bottom: 4em;
color: #fff;
background-color: #0072b0;
border-radius: .5em;
}
</style>
:::
垂直居中指南
在容器里让内容居中最好的方式是根据特定场景考虑不同因素。做出判断前,先逐个询问自己以下几个问题,直到找到合适的解决办法。
可以用一个自然高度的容器吗? 给容器加上相等的上下内边距让内容居中。
容器需要指定高度或者避免使用内边距吗? 对容器使用 display: table-cell
和 vertical-align: middle
。
可以用Flexbox吗? 如果不需要支持IE9,可以用Flexbox居中内容。
容器里面的内容只有一行文字吗? 设置一个大的行高,让它等于理想的容器高度。这样会让容器高度扩展到能够容纳行高。如果内容不是行内元素,可以设置为inline-block。
不知道内部元素的高度? 用绝对定位结合变形(transform)。
还不确定的话,可以试试 howtocenterincss。
负外边距
不同于内边距和边框宽度,外边距可以设置为负值。负外边距有一些特殊用途,比如让元素重叠或者拉伸到比容器还宽。
负外边距的具体行为取决于设置在元素的哪边:
- 如果设置左边或顶部的负外边距,元素就会相应地向左或向上移动,导致元素与它前面的元素重叠
- 如果设置右边或者底部的负外边距,并不会移动元素,而是将它后面的元素拉过来
- 给元素底部加上负外边距并不等同于给它下面的元素顶部加上负外边距
- 如果不给一个块级元素指定宽度,它会自然地填充容器的宽度
- 如果在右边加上负外边距,则会把它拉出容器。如果在左边再加上相等的负外边距,元素的两边都会扩展到容器外面
::: warning
如果元素被别的元素遮挡, 利用负外边距让元素重叠的做法可能导致元素不可点击。
:::
负外边距并不常用,但是在某些场景下很实用,尤其是当创建列布局的时候。不过应当避免频繁使用,不然网页的样式就会失控。
外边距折叠
当顶部和/或底部的外边距相邻时,就会重叠,产生单个外边距。这种现象被称作折叠。
文字折叠
外边距折叠的主要原因与包含文字的块之间的间隔相关。段落(<p>
)默认有 1em 的上外边距和 1em 的下外边距。这是用户代理的样式表添加的,但当前后叠放两个段落时,它们的外边距不会相加产生一个 2em 的间距,而会折叠,只产生 1em 的间隔。
折叠外边距的大小等于相邻外边距中的最大值。
多个外边距折叠
即使两个元素不是相邻的兄弟节点也会产生外边距折叠。在没有其他 CSS 的影响下,所有相邻的顶部和底部外边距都会折叠。
可以给任何元素加上外边距,而不必担心它们前后的元素是什么。只有当后面的元素需要更大的空间时,折叠外边距才会大于该元素外边距。
容器外部折叠
想要在容器内元素不与容器外元素外边距折叠,有下面方法可以处理:
- 当使用 Flexbox 布局时,弹性布局内的元素之间不会发生外边距折叠,网格布局也是
- 在两个外边距之间加上边框或者内边距,防止它们折叠
- 使用
overflow:auto
防止内部元素的外边距跟容器外部的外边距折叠,这种方式副作用最小 - 如果容器为浮动元素、内联块、绝对定位或固定定位时,外边距不会在它外面折叠
- 当元素显示为 table-cell 时不具备外边距属性,因此它们不会折叠。此外还有 table-row 和大部分其他表格显示类型,但不包括 table、table-inline、table-caption。
容器内的元素间距
容器的内边距和内容的外边距之间的相互作用处理起来很棘手。
::: demo [vanilla]
<html>
<body>
<div class="demo10">
<header>
<h1>Franklin Running Club</h1>
</header>
<div class="container">
<main class="main">
<h2>Come join us!</h2>
<p>
The Franklin Running Club meets at 6:00pm every Thursday at the town square.Runs are three to five miles, at your own pace.
</p>
</main>
<aside class="siderbar">
<a href="/twitter" class="demo10-btn-link">Follow us on Twitter</a>
<a href="/facebook" class="demo10-btn-link">Link us on Facebook</a>
</aside>
</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo10 h2::before {
all: initial !important;
}
.demo10 {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.demo10 header {
color: #fff;
background-color: #0072b0;
border-radius: .5em;
}
.demo10 .container {
display: flex;
width: 100%;
}
.demo10 main {
width: 70%;
background-color: lightgreen;
border-radius: .5em;
}
.demo10 .siderbar {
width: 30%;
padding: 1.5em;
margin-left: 1.5em;
background-color: lightgray;
border-radius: .5em;
}
.demo10 .siderbar .demo10-btn-link {
display: block;
padding: 0.5em;
color: #fff;
background-color: #0090c9;
text-align: center;
text-decoration: none;
text-transform: uppercase;
}
/* 只给紧跟在其他 btn-link 后面的 btn-link 加上顶部外边距 */
.demo10-btn-link + .demo10-btn-link {
margin-top: 1.5em;
}
</style>
:::
上述方法让一切如预期,但是如果在侧边栏添加更多内容,则会再次出现间距问题。为了应对更多变化,可以使用更通用的解决方案:猫头鹰选择器。
猫头鹰选择器的顶部外边距对侧边栏有个副作用。因为侧边栏是主列的相邻兄弟元素,所以它也会有顶部外边距。因此要将其恢复为 0,还需要给主列补上内边距。
::: demo [vanilla]
<html>
<body>
<div class="demo11">
<header>
<h1>Franklin Running Club</h1>
</header>
<div class="container">
<main class="main">
<h2>Come join us!</h2>
<p>
The Franklin Running Club meets at 6:00pm every Thursday at the town square.Runs are three to five miles, at your own pace.
</p>
</main>
<aside class="siderbar">
<a href="/twitter" class="demo11-btn-link">Follow us on Twitter</a>
<a href="/facebook" class="demo11-btn-link">Link us on Facebook</a>
<a href="/sponsors" class="demo11-sponsor-link">become a sponsor</a>
</aside>
</div>
</div>
</body>
</html>
<script>
</script>
<style>
.demo11 h2::before {
all: initial !important;
}
.demo11 {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.demo11 header {
padding: 1em 1.5em;
color: #fff;
background-color: #0072b0;
border-radius: .5em;
}
.demo11 .container {
display: flex;
}
.demo11 main {
width: 70%;
padding: 1em 1.5em;
background-color: lightgreen;
border-radius: .5em;
}
.demo11 .siderbar {
width: 30%;
padding: 1.5em;
margin-top: 0;
margin-left: 1.5em;
background-color: lightgray;
border-radius: .5em;
}
.demo11-btn-link {
display: block;
padding: 0.5em;
color: #fff;
background-color: #0090c9;
text-align: center;
text-decoration: none;
text-transform: uppercase;
}
.demo11 * + * {
margin-top: 1.5em;
}
.demo11-sponsor-link {
display: block;
color: #0072b0;
font-weight: bold;
text-decoration: none;
}
</style>
:::