行内元素与块级元素
行内元素
- 行内元素无法设置 weight 以及 width,大小通过内容决定
- 行内元素默认排列在同一行,除非内容宽度超过父容器的宽度
- 行内元素可以设置 padding 但是无法设置 marign
- 行内元素无法撑起父元素高度
块级元素
- 块级元素默认宽度为父容器的宽度,高度通过内容决定。可以通过 height 以及 width设置宽度和高度。
- 块级元素默认换行显示,就算宽度之和小于父元素
- 块级元素可以设置 margin 以及 padding
元素大小
- 无论是行内元素还是块级元素,设置 padding 后其元素的大小会改变。
- 块级元素宽度默认为父元素的宽度,高度默认为内容的高度。
- 当块级元素中包含行内元素时,行类元素设置 pading 属性可以改变自身的大小,但是无法撑起块级元素。例如对于如下代码:
<html>
<style>
#header{
background-color: black;
}
#header > span{
padding-top: 20px;
background-color: red;
}
</style>
<body>
<div id="header">
<span>123123</span>
<span>123123</span>
</div>
</body>
</html>
其显示结果为:
可以看出红色背景的 span 高度明显高于父 div 的高度。此时如果将 span 换成 p,则效果为:
可以看出此时父元素 div 的高度已经等于子元素 p 的高度。
盒模型
盒模型由四个部分组成:
- content
- border
- padding
- margin
其中 padding 是指 content 与 border 内边界的距离(border 有厚度,因此也需要分内外)。而 margin 则是指 border 的外边界与兄弟元素或父元素的 border 外边界的距离。当然对于行类元素无法设置 margin。一个典型的盒模型如下:
浮动
虽然浮动在 css 中常用来布局,但实际上浮动的设计初衷只是希望让行内元素能在环绕在浮动元素的周围。但由于设置了浮动后,浮动元素会脱离标准文档流。导致其后面的快级元素能够与浮动元素显示在同一行。因此才常用于浮动之中。
一种常见的需求是将两个块级元素显示在同一行。行类元素明明就默认显示在同一行,为什么需要块级元素呢?因为行内元素无法设置大小,也无法 margin。因此还是需要使用块级元素。
为了将两个元素显示在同一行,先定义以下代码:
<html>
<style>
#parent {
background-color: aqua;
padding: 20px;
}
#left {
background-color: red;
height: 100px;
/* float: left; */
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="parent">
<div id="left">132213213</div>
</div>
</body>
</html>
可以看出此时是正常显示,如果将 left 设置为 float:left。
<html>
<style>
#parent {
background-color: aqua;
padding: 20px;
}
#left {
background-color: red;
height: 100px;
/* float: left; */
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="parent">
<div id="left">132213213</div>
</div>
</body>
</html>
可以看出两个元素以奇怪的方式组合在一起。但是主要区别在两点:
- 子元素没有改变 width,但是子元素的宽度改变了。
- 父元素的高度与子元素不一致,虽然存在子元素。但是子元素没有撑起父元素的高度,父元素仿佛坍塌了一般。
这就是使用浮动带来的两个影响:
- 使用浮动后,如果元素不显式设置 width,则由其内容决定。这样设计的原因还是得从浮动得初衷谈起。因为浮动的初衷是让行内元素在其周围显示,如果设置浮动后其宽度仍然为默认的一行,则行内元素就无法在其周围显示。
- 设置浮动后,元素脱离标准文档流,无法撑起父元素的高度。
如果再给 left 添加一个兄弟元素,为了方便显示,给 left 添加宽度为 100px,兄弟元素宽度为 200px。
<html>
<style>
#parent {
background-color: aqua;
padding: 20px;
}
#left {
background-color: red;
height: 100px;
width: 100px;
float: left;
}
#div2{
background-color: blue;
width: 200px;
height: 100px;
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="parent">
<div id="left">132213213</div>
<div id="div2">132213213</div>
</div>
</body>
</html>
可以看出添加的兄弟元素并没有换行显示,而是与 left 显示在同一行。并且可以看出兄弟元素并没有显示完全,仿佛 left 浮在了兄弟元素的上方一样。这里即是设置浮动的第三个影响:设置浮动后元素脱离标准文档流,元素原来的位置会被未设置浮动的元素占据。
此时如果将兄弟元素也设置为 float: left。
<html>
<style>
#parent {
background-color: aqua;
padding: 20px;
}
#left {
background-color: red;
height: 100px;
width: 100px;
float: left;
}
#div2{
background-color: blue;
width: 200px;
height: 100px;
float: left;
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="parent">
<div id="left">132213213</div>
<div id="div2">132213213</div>
</div>
</body>
</html>
可以看出兄弟元素的宽度恢复正常。这是因为 left 以及其兄弟元素都脱离了标准文档流。因此兄弟元素不会占据 left 的位置。
但是如果 left 以及兄弟元素都设置为浮动,则此时他们共同的父元素没有一个可以支持的子元素,出现了坍塌。这显然是我们不希望的结果。这就需要用到清除浮动的方法。
一种简单的方法是在浮动元素后面加上一个不浮动的块级元素,并应用 clear: both。例如:
<html>
<style>
#parent {
background-color: aqua;
padding: 20px;
}
#parent::after{
display: block;
clear:both;
content: "";
}
#left {
background-color: red;
height: 100px;
width: 100px;
float: left;
}
#div2{
background-color: blue;
width: 200px;
height: 100px;
float: left;
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="parent">
<div id="left">132213213</div>
<div id="div2">132213213</div>
<div id="div3" style="clear: both;"></div>
</div>
</body>
</html>
其效果为:
清除浮动的原理其实很简单:
应用了此样式后,虽然 left 以及 div2 都已经浮动了,但是div3 仍然会出现在浮动元素的下方,而不是占据浮动元素的位置。这时,由于父元素需要包住 div3,顺便也包住了浮动的 left 以及 div2。看起来就像是父元素被撑起了一样。
虽然这样可以使得父元素被撑起来,但是其很难理解。因为只是添加了一个 div3,却影响到父元素。因此这种方法清除浮动更适合这样的场景。例如:
<div id="div1"><p1>left</p1></div>
<div id="div2"><p1>right</p1></div>
<div id="div3"><p1>footer</p1></div>
我们希望 div1 div2 分别向左右浮动,但是 div3 显示在 div1 的下方。当我们应用 div1 div2 向左右浮动的样式后:
由于浮动的原因,导致 footer 没有显示在合适的位置。这时候只要给 footer 应用 clear: both 即可正常显示。
clear 除了 both 还可以使用 left, right。即仅仅清除 left 以及 right 的浮动元素的影响。
利用 BFC 清除浮动
虽然使用 clear 可以简单的清楚浮动,在某些场景下虽然能够使用(例如使用空白 div 解决父容器坍塌的问题),但是破坏了布局与结构分离。并且 clear 并不能完全解决所有浮动相关的问题。例如一种常见的左边显示图片,右边显示文字信息的布局样式:
如果仅仅左边的图片浮动,例如:
<html>
<style>
#root{
margin-top: 20px;
margin-left: 20px;
width: 200px;
border: gray dashed 1px;
}
#div1{
width: 40px;
height: 40px;
border: black dashed 1px;
float: left;
font-size: 0.8em;
}
#div2{
font-size: 0.5em;
padding-left: 0.5em;
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="root">
<div id="div1"><p>图片</p></div>
<div id="div2"><p4>环绕文字环绕文字环绕文字环绕文字环绕文字环绕文字环绕文字环绕文字环绕文字环绕文字环绕文字环绕文字环绕文字环绕文字环绕文字</p4></div>
</div>
</body>
</html>
由于浮动的特性,右边的文字会环绕左边的图片,这明显不是我们想要的。但是如果我们给包含文字的 div2 应用样式 overflow: hidden 后显示结果就符合预期:
这是因为使用 overflow: hidden 后 div2 会创建 BFC。而对于一个 BFC 有以下特性:
- BFC 内的元素不会受其他 BFC 中浮动元素的影响
- BFC 会包含其中所有的浮动元素
当然,BFC 还有其他的性质,例如两个 BFC 之间不会存在外边距折叠。但是与上面的例子息息相关的主要还是这两点。因此我们对 div2 应用 overflow: hidden 后其创建了一个 BFC,使得里面的 p 标签不再受到 div1 浮动的影响。
同理,特性 2 还可以解决父元素高度塌陷的问题,例如:
<html>
<style>
#root{
margin-top: 20px;
margin-left: 20px;
width: 200px;
border: gray dashed 1px;
overflow: hidden;
}
#div1{
width: 40px;
height: 40px;
border: black dashed 1px;
float: left;
font-size: 0.8em;
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="root">
<div id="div1"><p>图片</p></div>
</div>
</body>
</html>
可以看出父元素在 div1 浮动的情况下仍然保持了高度,因为此时父元素作为 BFC 会将 div1 包含。这种方法要优于使用空白 div 的方式。因为使用了样式来解决父元素塌陷的样式问题,而不是使用增加结构来解决样式的问题。
flex 布局
使用各种 trick 的技术来清除浮动,终究还是因为 float 设计的初衷就不是用来布局的。但在各种主流浏览器支持 flex 之前,由于前端开发者不得不使用 float 来进行布局操作。但是现在浏览器都支持 flex 之后,我们终于可以忘记 float,拥抱 flex。接下来你会发现使用 flex 布局是多么的美妙。
一些基本概念
采用 flex 布局的元素称为 flex container(容器),其子元素自动成为 flex item(项目)。对于 flex 容器存在两条轴,分别是 main axis(主轴) 以及 cross axis(交叉轴)。这两根轴决定了 item 的默认排列方向。主轴和交叉轴并不是固定的概念,例如设置水平方向为主轴,那么竖直方向就为交叉轴。同理如果设置竖直方向为主轴,则水平方向就为交叉轴。
一个简单的例子
为了展示 flex 布局的威力,我们先假设这样一个场景。将块级元素水平分布在一行中:
<html>
<style>
#root{
margin-top: 20px;
margin-left: 20px;
width: 200px;
border: gray dashed 1px;
display: flex; /* flex 布局 */
justify-content: space-between; /* 主轴对其方式 */
}
.item{
width: 40px;
height: 40px;
border: black dashed 1px;
font-size: 0.8em;
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="root">
<div id="div1" class="item"></div>
<div id="div2" class="item"></div>
<div id="div3" class="item"></div>
</div>
</body>
</html>
我们仅仅只需要声明 root 为 flex 布局,并通过 justify-content 来设置对齐方式即可实现,非常的方便。说实话,我不太清楚这种样式通过 float 布局应该怎么实现。也许可以设置左右浮动,然后中间的元素居中对齐。但是使用这种方式需要一大堆清楚浮动的技巧。并且如果我想再增加一个元素,那么这种方法便失去作用。但是如果使用 flex ,如果需要增加元素,甚至不需要修改样式的代码。justify-content 会自动帮你计算合适的值。我只需要增加一行代码 <div id="div4" class="item"></div>
然后显示效果为:
非常的便捷。 justify-content 还有其他的选项:
- flex-start: 所有元素从主轴起始位置开始排列
- flex-end: 所有元素从主轴终止位置开始排列
- center: 所有元素中间排列
- space-around: 每个元素的左右空间相等
- space-between: 把元素排列好之后的剩余空间拿出来,平均分配到元素之间,所以元素之间间隔相等
控制 item 宽度
很多时候我们不希望硬编码每个元素的宽度,而是自动的分配每个元素宽度。在 flex 中可以通过 flex 样式来控制 flex 样式有几个基本的概念:
flex-basis: 元素的基本宽度。如果元素设置了 width,则该值为 width。否则为 auto,即根据元素内容自动计算。
flex-grow: 如果当元素的宽度不足以填满容器剩下的空间,则剩下的空间按该值的比例分配给元素。例如三个元素分布设置 2 1 1。剩余 400px。则第一个元素会分配到 200px,其余两个元素会各自分配 100 px。
flex-shrink: 同理,当元素的宽度操作容器的空间。该值觉得元素被压缩的比例。
这个三个值可以单独指定,也可以通过 flex: flex-grow flex-shrink flex-basis 的方式一同指定。例如下面的代码:
<html>
<style>
#root{
margin-top: 20px;
margin-left: 20px;
width: 200px;
border: gray dashed 1px;
display: flex;
}
.item{
height: 40px;
border: black dashed 1px;
font-size: 0.8em;
flex: 1 1 auto;
}
#root > div + div { /* 选择所有 div 后面的 div 即不包含第一个 div*/
margin-left: 1em;
}
#div1{
flex: 2 1 auto;
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="root">
<div id="div1" class="item"><p>a</p></div>
<div id="div2" class="item"><p>b</p></div>
<div id="div3" class="item"><p>c</p></div>
</div>
</body>
</html>
可以看出 div1 的宽度明显宽于 div2 以及 div3。
交叉轴对齐
曾经有这样一个 css 笑话:
我: 我十年前端经验,能熟练掌握各种排版布局技巧
面试官: 请你在不使用搜索引擎的情况下写一个垂直居中对齐
我: 告辞
这个笑话的真实程度我们不得而知但是由管窥豹,可见在 float 布局时代写一个垂直居中到底有多难。但是在 flex 中一切都很简单:
<html>
<style>
#root{
margin-top: 20px;
margin-left: 20px;
width: 400px;
border: gray dashed 1px;
display: flex;
align-items: center;
}
.item{
border: black dashed 1px;
font-size: 0.8em;
flex: 1 1 auto;
text-align: center;
}
#root > div + div { /* 选择所有 div 后面的 div 即不包含第一个 div*/
margin-left: 1em;
}
#div1{
flex: 2 1 auto;
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="root">
<div id="div1" class="item"><p>a</p></div>
<div id="div2" class="item"><p>b</p></div>
<div id="div3" class="item">
<p>
This is text
<br/>
This is text
<br/>
This is text
<br/>
</p>
</div>
</div>
</body>
</html>
div3 撑起了父元素的高度,div1 以及 div2 自动在垂直方向居中对齐。
在 flex 中使用 align-items 进行交叉轴的对其控制。其值包含:
- align-items: flex-start flex 项目的开始端的对齐
- align-items: flex-end flex 项目的结束端对齐
- align-items: center flex 项目居中对齐
- align-items: stretch flex 项目撑满 flex 容器
- align-items: baseline flex 项目的基线对齐
当然,在 float 中同样需要无数 trick 实现的等高列,在 flex 中只需要 align-items: stretch 即可。这里的 align-items 是控制所有 item 的交叉轴对齐方式。 还可以使用 align-self 单独控制某个 item 的交叉轴对齐方式,可用的选项与 align-items 相同。
定位
很多人抱怨 CSS 不好学,其最终的原因其实大部分是因为 CSS 的各个功能不正交。例如要实现一个元素水平居中显示,可能有许多种方法。同时,对于 布局 以及 定位 两个概念也有很多混淆的地方。尤其是在 float 实现布局的时候,感觉这个功能既能够通过 float 实现,也能够通过 position 实现。
实际上,布局更强调各个元素组合起来呈现的视觉效果。而定位则更强调一个元素应该出现在某个位置。例如,常提到的圣杯布局,双飞燕布局都是多个元素组合起来呈现的效果。而定位常见的案例则是如果存在未读消息,在头像右侧显示一个小红点,以及在屏幕的右下角显示一个固定位置的导航窗格等。
static 定位
默认值,此时设置 left,right,top,bottom 等属性无效
固定定位
position: fixed 设置为固定定位。位置相对于屏幕可视窗口进行定位,比较简单。这里不多说。
绝对定位:
position: absolute 为绝对定位。元素会从标准文档流中移除。然后相对该元素的第一个设置了非 static 定位的父元素位置进行偏移来确定位置。例如:
<html>
<style>
#root{
margin-top: 20px;
margin-left: 20px;
width: 400px;
border: gray dashed 1px;
display: flex;
align-items: center;
position: relative;
}
#root > div + div {
margin-left: 1em;
}
.item{
border: black dashed 1px;
font-size: 0.8em;
flex: 1 1 auto;
}
#div2{
position: absolute; /* 绝对定位 */
left: 0px; /
margin-left: 0px !important;
}
* {
margin: 0px;
padding: 0px;
border: 0ch;
}
</style>
<body>
<div id="root">
<div id="div1" class="item"><p>a</p></div>
<div id="div2" class="item"><p>b</p></div>
<div id="div3" class="item">
<p>
This is text
<br/>
This is text
<br/>
This is text
<br/>
</p>
</div>
</div>
</body>
</html>
可以看出 div2 以及 div1 重合在了一起,并且 div2 并未参加 flex item 宽度的计算。这里可以看出两个信息:
- div2 脱离了标准文档流,因此 root 此时只存在 div1 div3 两个 flex item
- div2 位置紧靠 root 的左侧,因为设置了 left 为 0
相对布局
position: relative 为相对布局。其实相对布局这个名字比较容器有误导性,第一次看的时候以为相对布局是相对父元素或者相对兄弟元素布局。事实上,相对布局是相对元素在标准文档流的位置进行布局。即相对布局的计算方式是先假设这个元素设置的布局方式为 static 计算这个元素在标准文档流中的位置,再根据这个位置结合偏移量进行定位。例如进行如下修改后:
#div2{
position: relative;
left: 20px;
margin-left: 0px !important;
}
/* #root > div + div {
margin-left: 1em;
} */
这里 div2 实际上是根据 div2 未设置相对定位的位置向右移动了 20px,因此和 div3 存在一定交叉。并且在去掉 margin 之后 div1 与 div2 仍然有一定的距离,原因是因为 div2 所在标准文档流中的位置仍然存在,不会被其他元素占据。使用相对布局有以下几个需要注意的地方:
- 使用相对布局,并且不设置任何位置信息,该元素的位置不会发生任何改变
- 相对布局改变位置后,元素所在标准文档流中的位置仍然存在,不会被其他元素所占据。
- 相对布局不是相对父元素或者兄弟元素进行布局,而是使用相对标准文档流的位置进行布局。
一些实例
使用绝对布局实现头像红点
<html>
<style>
#img{
width: 50px;
height: 50px;
border: black dashed 0.5px;
position: relative;
margin-top: 10px;
margin-left: 10px;
}
#circle{
width: 10px;
height: 10px;
background: red;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
position: absolute;
right: -5px;
top: -5px;
}
*{
border: 0ch;
padding: 0;
margin: 0;
}
</style>
<body>
<div id="img">
<div id="circle">
</div>
</div>
</body>
</html>
使用相对定位画两个交叉的圆形
<html>
<style>
.circle{
width: 100px;
height: 100px;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
position: relative;
}
#circle1{
background-color: blue;
left: 10px;
}
#circle2{
background-color: red;
right: 10px;
}
*{
border: 0ch;
padding: 0;
margin: 0;
}
</style>
<body>
<div style="display: flex; margin-top: 20px;">
<div id="circle1" class="circle"> </div>
<div id="circle2" class="circle"> </div>
</div>
</body>
</html>
网友评论