CSS 相对复杂但实用的margin
margin 可以理解为盒子的外边距,决定了盒子与盒子之间的距离。 margin包围的盒子尺寸一般称为盒子的"外部尺寸"。
外部尺寸和内部尺寸(可视尺寸)和 偏移尺寸 不同,其值可以为负,这就给margin带来了很多有意思的现象和应用,我们常用的左右布局,双飞翼布局,圣杯布局,等高布局,都可以采用负的margin盒子完成!
复习一下: 外部尺寸 内部尺寸 偏移尺寸 (滚动尺寸)
- 外部尺寸,即margin所包围的盒子,可以立即为元素所占的空间,没有原生的DOM API可以获取这个尺寸
- 内部尺寸,也称为元素的可视尺寸,其范围是border-box的内边缘,也就是padding-box的范围 对应DOM API为 clientWidth clientHeight (注意 行内元素没有这两个尺寸)
- 偏移尺寸, 即borderbox外边缘对应的尺寸 DOM API对应的是 offsetWidth offsetHeight
- 滚动尺寸,可以理解为元素的内部尺寸+超出部分的尺寸,对应DOM API为 scrollWidth scrollHeight (行内元素没有这个尺寸) ,如图所示
一个很有意思的现象是,当我们在chrome firefox safiri 浏览器中运行想通的html,其获得的滚动尺寸不相同, 因为在safiri浏览器中,其border-bottom也会被计算到scrollBottom中,但是chrome和firefox明显没计算,这其实是一个非标的问题,各个浏览器如何实现都是正确的,开发人员需要注意这个差异,尽可能不被这个差异影响。
margin影响元素尺寸
我们知道,margin的作用范围在盒子尺寸之外,也就是说,如果盒子设置了width height 不论此时的box-sizing是什么值,其margin都无法改变盒子的尺寸。
margin如果想影响盒子的尺寸 (这里方便描述,我们假设padding border都是0px,宽度高度就是content-box的尺寸)需要满足两个条件
1. 宽度高度没有被设置
2. 元素的外部尺寸生效,即元素必须处于自动填充空间的状态,而不能是内部尺寸,即包裹内容的状态。
这两个条件满足后,为了保证盒子的外部尺寸 = margin+border+padding+宽度/高度 盒子会自动调整content-box的宽高来让这个等式成立。
看一个例子,中间盒子的内容由粉色部分占满, 此时中间盒子的 margin为0px
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.container {
float: left;
width: 400px;
height: 300px;
border: 1px dashed black;
}
.box {
height: 80%;
margin-right: 0px;
margin-left: 0px;
background-color: pink;
}
.container2 {
float: left;
width: 400px;
height: 300px;
border: 1px dashed black;
background: skyblue;
}
</style>
</head>
<body>
<div class="container2"> </div>
<div class="container">
<div class="box"></div>
</div>
<div class="container2">
</div>
</body>
此时粉色部分由于是块元素,其外部尺寸起作用,刚好占满中间盒子的宽度。
此时,我们设置中间盒子的左右margin,如下:
.box {
height: 80%;
margin-right: 20px;
margin-left: 40px;
background-color: pink;
}
可以发现粉色部分的宽度变小,因为margin占用了空间,为了保证外部尺寸=margin+padding
+border+宽高的等式,所以width被缩小了60px
如果我们把margin设置为负值,如下:
.box {
height: 80%;
margin-right: -20px;
margin-left: -40px;
background-color: pink;
}
可以看到,粉色部分左右被“延长”以补充负的margin值,此时中间box区域的外部尺寸还是400px
前后元素的位置不被影响是因为在决定外部尺寸的几个尺寸中,宽度width是自适应的,浏览器就会尽可能的用调整宽度的方式保证元素填满包含块
如果 我们把宽度定死,那么此时负的margin就会影响到盒子的外部尺寸了,因为没有可以调整的值来占满空间了。如下,粉色格子因为margin和前后元素发生重叠!
margin布局应用
上面我们说了,在尺寸没有被设置并且元素呈外部尺寸(填充容器)的情况下,margin可以自动调整元素的尺寸,以保证容器内容区的尺寸 = 内容margin+padding+content!
消除浮动右margin
我们使用浮动的时候,通常会用margin的方式留出浮动元素之间的空隙,但是存在的问题是,我们不希望最右侧的元素也包含margin-right
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.container {
display: inline-block;
height: 200px;
margin: auto;
background-color: lightgoldenrodyellow;
}
.float {
float: left;
width: 100px;
height: 100%;
background-color: lightskyblue;
margin-right: 10px;
}
.box {
display: inline-block;
height: 200px;
width: 200px;
background-color: lightpink;
}
</style>
</head>
<body>
<div class="container">
<div class="float">float: left</div>
<div class="float">float: left</div>
<div class="float">float: left</div>
<div class="float">float: left</div>
</div><div class="box"></div>
</body>
如图所示,最右侧元素包含右侧的margin,导致后面的元素也被向后挤了10px的空间。
通常的做法是使用 :last-child等伪类来消除掉这个margin,这里介绍一个更好的方法
我们设置container的margin-right为-10px
.container {
display: inline-block;
height: 200px;
margin: auto;
background-color: lightgoldenrodyellow;
margin-right: -10px;
}
这样可以让container形成一个右侧10px的负margin,由于container元素为行内块元素,虽然没有设置尺寸,但是不满足外部尺寸的条件,此时container元素的内部宽度不会被自动调整,导致整个元素的外部尺寸缩小了10px。
这样右侧的10px空间就被利用上了,如下:
我们再看一个宽度自动调整的例子
我们设置一个宽度430px的outer-container 其中包含一个子 container,盛满宽度,然后container内部设置宽度100px margin-right:10px的浮动
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.outer-container{
width: 430px;
height: 200px;
}
.container{
height: 100%;
background-color: lightblue;
}
.box{
float: left;
width: 100px;
height: 100%;
background: orange;
margin-right: 10px;
}
</style>
</head>
<body>
<div class="outer-container">
<div class="container">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
</div>
</body>
可以看到,由于container元素总宽度为 (100 + 10) * 4 = 440px 所以明显outer-container的430px宽度是不够的
这是我们依旧可以通过负margin 把最后一个元素的margin-right: 10px 利用上,让最后一个元素上去
解决办法是,由于container满足 1. 自动宽度 2. 外部尺寸 的特性,所以可以通过设置container的margin-right: -10px 让其内容盒子尺寸增加10px
.container{
height: 100%;
background-color: lightblue;
margin-right: -10px;
}
此时可以看到 container的内容盒子宽度增加了10px
此时盒子上去了,但是右侧margin露出来了,虽然container的宽度变成了440px,但是outer-container的宽度还是430px
我们可以直接设置outer元素overflow: hidden 即可解决!
margin解决等高问题
什么是等高问题,一个容器内左右两个盒子,两个盒子和容器的高度都取决于其中最高的盒子。
解决等高度问题有很多种办法,包括 flex 布局 ,gird布局,border模拟定高,-margin结合padding,以及table布局
这里先说一个margin结合padding的定高方法
其实与其说是定高,不如说是模拟定高,我们看一个例子
这个例子中,我们点击左右按钮分别向左右盒子中添加item,但是不论左右盒子中元素有多少,两个盒子的高度一定是相等的。如何做到?
HTML结构很简单,左右两个元素,并且都设置浮动,宽度50%
<div class="container">
<div class="left"></div>
<div class="right"></div>
</div>
其中,关键点在,把左右元素的margin-bottom都设置为一个很大的值 如 -9999px 此时左右元素的外部容器盒子高度都是 - 9999px。 此时虽然元素的高度没有设置,为自动高度,但是不满足自动填充包含块元素这个特性,也就是说,高度的大小由其包裹的内容决定,此时浏览器不会自动调整高度保证盒子的外部容器高度不变。
此时,我们需要手动设置padding-bottom为9999px,相当于用人为用padding-bottom填满了-9999px的margin-bottom!此时 元素的尺寸依旧保持不变,但是其多了9999px高度的可视区域
.container {
background-color: lightblue;
position: relative;
}
.left {
float: left;
width: 50%;
background-color: lightcoral;
margin-bottom: -9999px;
padding-bottom: 9999px;
}
.right {
float: right;
width: 50%;
background-color: lightgoldenrodyellow;
margin-bottom: -9999px;
padding-bottom: 9999px;
}
此时,你会发现盒子异常的高!
但是我们需要的是动态的调整高度一致,解决的办法也很简单。
我们知道,调整之后的左右盒子高度其实依旧不变。其高度由其内部的内容高度决定,由于外部contianer的高度是由left/right两个盒子中最高的决定,这就解决了我们的问题,设置container的overflow: hidden即可!
加上一些JS控制,实现效果!
margin实现左右布局
1. 左侧固定,右侧自适应
这种布局很简单,只需要让左侧元素左浮动,右侧元素借助流的特性自适应大小即可。
<style>
.left-container {
height: 300px;
background-color: lightcyan;
}
.left-container>.left {
width: 200px;
height: 100%;
background-color: lawngreen;
float: left;
}
.left-container>.right {
height: 100%;
background-color: yellow;
margin-left: 200px;
}
</style>
<h3>左侧固定 右侧自适应</h3>
<div class="left-container">
<div class="left"></div>
<div class="right"></div>
</div>
这里需要注意几点
1. 右侧为一个块盒子,需要借助块盒流动的特性,盛满剩余空间,并且不要设置其宽度为100% 否则整个右侧盒子加上margin的宽度为 100% container宽 + 200px的margin (无宽度原则)
2. 右侧盒子不要设置为浮动,因为浮动元素为包裹特性,无法占满剩余空间,需要设置 width: 100% 但是这样宽度由会超出
3. 不要左右元素使用 inline-block 因为会出现空格 (换行导致 和代码书写格式相关了)
2. 右侧固定 左侧自适应
第一种实现,其html结构如下
<h3>右侧固定 右侧自适应 右浮动实现</h3>
<div class="right-container-2">
<div class="right"></div>
<div class="left"></div>
</div>
右侧盒子在前面,因为浮动的特性是,浮动元素无法覆盖其上面的元素,如果left在前面,由于其是块元素占满一行,right元素永远无法和left一行显示。
.right-container-2 {
height: 300px;
background-color: lightcyan;
}
.right-container-2>.left {
height: 100%;
background-color: lawngreen;
margin-right: 200px;
}
.right-container-2>.right {
float: right;
height: 100%;
background-color: yellow;
width: 200px;
}
这样的缺点是,left right的html结构和其位置相反,如果需要顺序相同,就需要让left元素也浮动起来,并且通过右侧margin给right留出空间,即可
.right-container {
height: 300px;
background-color: lightcyan;
}
.right-container>.left {
height: 100%;
width: 100%;
background-color: lawngreen;
float: left;
margin-right: -200px;
}
.right-container>.right {
float: left;
height: 100%;
background-color: yellow;
width: 200px;
}
margin实现左右宽度固定,中间自适应布局
这种布局也就是我们常说的 圣杯 & 双飞翼 也是面试的高频考点
分析一下,由于两个固定一前一后展示,所以中间元素一定不能是块元素,因为后面的元素永远无法和块元素占同一行!所以我们让三个盒子都左浮动。
center盒子需要在html结构的最前面,以达到优先渲染的目的
第一种,圣杯布局
我们让容器元素设置左右的padding给两侧的盒子预留出空间,中间元素占满100%的容器内容空间如下:
<style>
.centered-container {
height: 300px;
background-color: lightcyan;
padding: 0px 200px;
}
.centered-container>.center {
float: left;
background-color: orange;
height: 100%;
width: 100%;
}
.centered-container>.left {
float: left;
height: 100%;
background-color: greenyellow;
width: 200px;
}
.centered-container>.right {
float: left;
height: 100%;
background-color: yellow;
width: 200px;
}
</style>
<h3>左右固定 中间自适应</h3>
<div class="centered-container">
<div class="center"></div>
<div class="left"></div>
<div class="right"></div>
</div>
效果如下:
此时,我们需要左右元素浮动到第一行显示。
但是由于此时center盒子已经占满空空间了,左右盒子没有空间所以移动到第二行,我们需要做的就是,让左右盒子占的空间为0px,这就可以用负margin解决
我们设置左边盒子的margin-left为-200px 此时左边盒子占空间为0px 如图所示
可以看到,margin-left在减少盒子占空间时,还会把盒子往前拉。当盒子占宽度为0px时,margin盒子本来应该出现在红色框的位置,但是由于margin-left负值的"副作用",其向左移动了200px
借助这个副作用,我们可以设置margin-left: -100% 让其向左移动包含块容器(也就是container内容区)的100%长度,此时左盒子出现在下面位置
此时,我们还需要左盒子继续向左移动一点,这是可以用相对定位,将左边盒子的left设置为-200px,如下:
.centered-container>.left {
float: left;
height: 100%;
background-color: greenyellow;
width: 200px;
margin-left: -100%;
position: relative;
left: -200px;
}
左边盒子调整完了,我们调整右边盒子,这个就比较简单了,我们需要右边盒子占的宽度为0px 并且不需要移动位置,此时可以用margin-right: -200px
.centered-container>.right {
float: left;
height: 100%;
background-color: yellow;
width: 200px;
margin-right: -200px;
}
这就达成了效果
圣杯布局的缺点是,由于其左侧盒子通过container内容区的-100% 实现向左移动,当盒子缩小到container内容区域宽度< 盒子的宽度 200px时,此时container内容区宽度 * -100% < 200px 此时左盒子的宽度就不为0了 (200 - container.width * 100%)这时左盒子会因为有宽度掉到第二行,并且右盒子由于在左盒子后面,浮动不能超过前面的元素,也会掉下去,如图:
造成这个现象的原因就是,左盒子的 -100% 计算的对象是其包含块的内容盒子宽度,即container的宽度,如果我们相对的是container的border-box计算,就没有内容区过小导致定位错误的问题了,这就是双飞翼布局解决的问题。
双飞翼布局不再让container设置padding来给左右盒子流出空间了,而是让中间center盒子直接占满整个container,此时的左盒子的 -100% 就是相对contianer的border-box计算了,如下:
<style>
.centered-container-2>.center {
float: left;
background-color: orange;
height: 100%;
width: 100%;
}
.centered-container-2>.center>.content{
margin: 0px 200px;
background-color: violet;
height: 100%;
}
.centered-container-2>.left {
float: left;
height: 100%;
background-color: greenyellow;
width: 200px;
margin-left: -100%;
}
.centered-container-2>.right {
float: left;
height: 100%;
background-color: yellow;
width: 200px;
margin-left: -200px;
}
</style>
<h3>双飞翼</h3>
<div class="centered-container-2">
<div class="center">
</div>
<div class="left"></div>
<div class="right"></div>
</div>
这样带来的另外一个好处是 左元素直接 -100%的margin-left就能移动到最左侧了,不需要在用相对定位了!
但是带来的一个问题是 ,center元素的内容会被左右盒子覆盖,因为其宽度占满整个container空间
解决办法也很简单,第一种是设置center的box-sizing为border-box,并且设置padding: 0px 200px;
第二种办法就是在center内增加内容子元素,并且设置子元素的margin为0px 200px即可
.centered-container-2>.center>.content{
margin: 0px 200px;
background-color: violet;
height: 100%;
}
<h3>双飞翼</h3>
<div class="centered-container-2">
<div class="center">
<div class="content">
双飞翼双飞翼双飞翼双飞翼双飞翼双飞翼双飞翼双飞翼双飞翼双飞翼
</div>
</div>
<div class="left"></div>
<div class="right"></div>
</div>
margin 移动盒子
如果margin为正, 上方和左侧的margin会把盒子向下或向右推,右侧或下侧的margin不会改变当前元素的位置,会把后面的元素向右或者向下推. 如图
如果margin为负,则left top方向的负margin会把元素向左边 上边拉 后面的元素也会跟着走
right bottom的margin不会移动本元素,但是会把右后方和下方的 元素往左往上拉
margin和行内元素
我们知道 padding在水平方向上可以影响行内元素的位置,垂直方向上不会影响位置,但是会导致overflow auto出现滚动条,有颜色,可以扩大点击区域等等
margin在水平方向可以起作用,但是垂直方向没有任何作用, 不能增大点击区域,更不能让父元素出现滚动条
margin百分比
和padding一样,margin的百分比也是相对于包含块的宽度计算的,但是需要注意考虑到margin合并的问题,如果我们想用margin撑开一个1:1的正方形,最后得到的一定是一个 2:1 的长方形,因为在垂直方向上,由于内容为空,发生上下margin合并。
正确看待margin合并
margin合并的条件
1. 必须是块元素,才会发生margin合并
2. 只有垂直方向的margin才会发生合并,水平方向不会发生合并
margin合并的三种情况
1. 父元素和其第一个子元素的上margin会合并,父元素和其最后一个子元素的下margin会合并
2.相邻元素的margin上下margin会合并
3.空块元素的margin会合并
我们看下面的例子,通过设置padding 和 margin为200px来画一个边长为400px的正方形
<!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>
.box {
display: inline-block;
border: 1px solid black;
background-color: lightgreen;
}
.inner {
margin: 200px;
}
.inner2 {
padding: 200px;
}
</style>
</head>
<body>
<div> margin: 200px;</div>
<div class="box">
<div class="inner"></div>
</div>
<div>padding: 200px;</div>
<div class="box">
<div class="inner2"></div>
</div>
</body>
</html>
最终效果如下, 可以看到 margin: 200px 形成了一个 2:1的矩形,这就是因为inner的内容为空,所以其上下margin发生合并了
想要解决也很简单,设置给inner设置一些内容即可阻止合并
但是阻止合并之后的正方形也并不是长宽相等的,因为内容会导致空白幽灵节点的生成,其高度会更高一点, 所以如果你想要生成正方形,还是建议使用padding!
如何禁止margin合并
父子合并
1. 父元素设置border padding分离内部margin和外部margin, 注意,不要给子元素设置,因为子元素的border和padding无法分割子元素的margin和父元素的margin
2. 父元素开启BFC
属于同一个BFC的父子元素才会合并,把父元素单独生成一个BFC可以创建一个独立的渲染区域,可以解决合并问题
兄弟合并
对于兄弟元素之间的合并,用border或者padding无法解决,因为无法分割两个margin
我们可以在盒子的外层包一层div,并且设置其border即可
我们把外层div设置为BFC也可以解决
一个BFC内部的两个元素会发生margin合并,这种做法就是创建一个新的BFC将两个元素分隔到不同的BFC中。
margin合并意义
css最初设计就是为了文本排版而生,margin合并不是一个bug,可以算是一个特性,其作用就是让文版拍版更方便,兄弟盒子margin合并的意义在于,我们不必特殊处理单行文本的margin或者两行文本的margin到底相加是多少的问题,浏览器会自动处理间距。
父子margin合并的意义在于,如果在某一行文本外添加一个容器,只要容器的margin没有更大,那么其margin依旧保持不变,不会因为增加容器导致某一行没有了间距
空盒子自己margin合并的意义在于,不会因为作者漏加了某个空元素,导致间距不一致的问题,空元素其实发生了两次margin合并,即空元素和上下元素合并一次,空元素自己合并一次!
所以,任何css特性都有其存在的意义,我们需要了解 并且根据需要作出必要的处理!