margin
1. 是什么
margin 外边距,是元素外部空出的空间,让元素与元素之间有适当的间隔;
margin 属性可以给元素设置四个方向的外边距属性,即 上右下左。margin 是 margin-top、margin-right、margin-bottom、margin-left 的简写;每个值的取值可以是固定值,百分比 或 auto;
1.1 margin 中 top、right、bottom、left 的参考线
它们的参考线分为两类,top、left 是一类;bottom、right 是一类;
top 以包含块的 content 的上边或垂直上方的相连元素的 margin 的下边为参考线垂直向下或向上移动,会导致元素位置移动;
left 以包含块的 content 的左边或水平左边的相连元素的 margin 的右边为参考线水平向右或向左移动,会导致元素位置移动;
bottom 以元素本身的 border下边为参考线垂直向下或向上移动,可能会导致相邻元素位置移动;
right 以元素本身的 border 右边为参考线水平向右或向左移动,可能会导致相邻元素位置移动;
1.2 固定值的正负取值
-
正值
top left 正值会让元素相对之前的位置向下向右移动;
right bottom 正值会增加和相邻元素的空白距离,自己不动,水平右边和垂直下边的相邻元素会远离元素; -
负值
top left 负值会让元素相对之前的位置向上向左反方向移动;
right bottom 不会让元素移动 margin 会和元素content 重叠 (负值可能会让邻居元素更接近自己,覆盖自己。当然,元素本身的 width、height 并没有发生改变。)盒子的总宽高 = content + padding + border + margin;
如果盒子设置了 width 属性,宽是确定的,设置负的 margin-right、margin-bottom 只会改变盒子 right、bottom 的参考线。
如果盒子的宽是继承父元素的 width,设置负的 margin-right、margin-bottom ,盒子的 content 的宽高会在原有的数值上加上设置的 margin-right / margin-bottom 的绝对值。图1(左)是未加 margin 的盒子位置;图1(中)盒子添加了 margin:20px;可以看出 margin 的空白区域的添加规则,top 正值是以原有 content- top 为基准线,向下添加,left 正值是以原有 content- left为基准线,向右添加,bottom 正值是以 border- bottom为基准线,向下添加,right 正值是以 border- right为基准线,向右添加,
1.3 百分比值的参考
margin 的取值也可以是百分比,这个百分比参考的是包含块的 width;
<style>
body {
margin: 0;
}
.box {
width: 500px;
height: 300px;
background-color: palegreen;
}
.son {
width: 50%;
height: 50%;
margin: 10% 5%;
background-color: pink;
}
</style>
<div class="box">
<div class="son">子元素</div>
</div>
son 的包含块是 box,
son width = box width * 50% = 500 * 50% = 250px;
son height = box height * 50% = 300 * 50% = 150px;
son margin-top / bottom = box width * 10% = 500 * 10% = 50px;
son margin-left / right = box width * 5% = 300 * 5% = 15px;
width、margin、padding、left、right 的百分比值,是相对于包含块的 width 计算的。
height、top、bottom 的百分比值,是相对于包含块的 height 计算的。包含块的 height 如果是通过内容变化的,且 position 值为 static / relative,百分比值为 auto,适应内容的高度。
(默认的 writing-mode: horizontal-tb; 和 direction: ltr; 的情况下,当书写模式变纵向,参照会交换成包含块的 height / width)
1.4 深挖 auto
margin: 0 auto; / margin:0 auto 0 auto;
margin: auto; / margin: auto auto auto auto;
根据CSS规格:如果 margin-top 或 margin-bottom 为 auto,则其使用值为0;
所以二者虽然写法不同,但实际执行结果一致,使得块元素水平居中。
auto 属性值可以用在 width、margin-left、margin-right属性上;
auto 的结果可以是 0px、自动填充剩余空间、100%;auto 的取值具有不确定性,结果依赖于浏览器自己选择。有时,在一些特殊情况下,该值可以使元素居中。
块元素的总宽度 = 左右 margin + 左右 border + 左右 padding + content = 包含块的 width;
这个公式告诉我们,块元素的总宽度,必须刚好等于包含块的宽度;同时,这个公式是有条件的。
- width 为 auto,那么其他 auto 值(margin)为 0;
- margin-left、margin-right 值均为 auto,如果有剩余空间,那么二者平分剩余空间,让元素水平居中。
从条件可以知道,如果 width:auto,那就没有 margin 什么事情了;如果 margin 值为 0 auto 或 auto ,那么 left、right 就均分剩余空间,让块元素水平居中。
margin:auto 为什么能实现水平居中?
因为水平方向的 auto,其计算值平分了可用空间(剩余空间)。
那么,如果 left / right 仅有一个值为 auto,auto 会让该属性占满剩余可用空间,margin-right:auto;的结果就相当于右对齐了。
你可能会奇怪,为什么 top、bottom 都是 auto 就当做 0 算,没有垂直平分居中呢?
这与书写模式 writing-mode 、文档流布局规则和方法 direction 有关:
我们目前的主流版本是横排文字,其水平宽度一定(如果没有显式定义宽度或强制一行显示,都会遇到边界换行,而不是水平延伸)垂直方向可以无限延展。
正是因为垂直方向是无限延展的,没有确定值,所以就没有参照值可以用来计算垂直方向的 margin、height。这也就是为什么说 margin-top / margin-bottom 值为 auto 时,auto 当做 0 px 算。height:auto;auto 跟随内容高度的大小。
如果改变排版布局,改成竖排文字,此时就是垂直方向高度 height 一定,水平方向无限延展。
受书写模式影响的特性:1. margin 垂直方向塌陷问题;2. margin auto ;3. margin 、padding 的百分比值;
2 margin 折叠(塌陷)问题
前面提到,margin 折叠问题是因为排版引起的。这是因为在大多情况下,折叠垂直方向上的 margin 在视觉上可以显得更美观,也更贴合设计师的预期。
外边距用来指定非浮动元素与其周围盒子边缘的最小距离。两个或两个以上的相邻的垂直外边距会被折叠并使用它们之间最大的那个外边距值。多数情况下,折叠垂直外边距可以在视觉上显得更美观,也更贴近设计师的预期。 ---- css1 margin 译文
在水平书写模式下,发生 margin 折叠的是垂直方向,即 margin-top/margin-bottom,在垂直书写模式下,margin 折叠发生在水平方向上,即 margin-right/margin-left。
从上面的话,我们可以提取一些关键信息:
- 发生折叠的是非浮动元素,浮动元素和其它元素在 margin 相邻时不会发生折叠;
- 书写模式决定着 margin 折叠方向,默认是水平书写模式,margin 折叠是垂直方向,垂直书写模式,则是水平方向折叠;
- margin 折叠取较大值;
避免 margin 折叠(塌陷)方法:
- 兄弟元素 margin 塌陷
浮动元素、绝对定位元素和兄弟 margin 相邻不会发生折叠问题;
垂直方向,只给一个元素设置 margin 值,避免折叠问题; - 父子之间 margin 塌陷
根元素和其子元素不存在父子之间 margin 折叠;
为父元素添加 border / padding,这样父子盒子就不再完全贴合了;
定义了属性overflow且值不为visible(即创建了新的块级格式化上下文)的块元素,不与它的子元素发生margin 折叠;
父级定位元素 position 值为 absolute / fixed;
改变元素类型:父元素添加 display: table / flex / grid / inline-block;
伪元素父元素::before { content: ''; display: table; }
3. margin 用法
-
让元素之间有合适间距;
-
让块元素水平居中;
流式布局(float + margin:auto);
float + margin 负50%;
绝对定位 + margin; -
处理特殊 first、last 元素;
<style>
li{
height: 30px;
line-height: 30px;
border-bottom:1px solid #95ef85;
}
</style>
<div class="box">
<ol>
<li>哇,我有下划线 (*^w^*)</li>
<li>嗯啊嗯啊,我也有下划线 (=^-^=)</li>
<li>不就是一个下划线,谁还没有啊 (-z-)...</li>
<li>我,我没有 --(T ^ T)--</li>
</ol>
</div>
处理最后一个 li 的下划线的方法有很多,可以通过 css 选择器 last-child 等;也可以单独给最后一个 li 添加一个类;这里重点说一下 margin 怎么处理 last 下划线;
.box {
overflow: hidden; }
ol {
margin-bottom: -1px; }
ol margin-box 值为 -1px,ol 高度不变,但是 ol 底部的外边距向上移动了 1px,刚好赶到 last 的 下划线处。紧邻的 box 的区域也向上缩短了 1px 的面积,再设置 overflow: hidden; last 的 下划线被 hidden 隐藏了。
- 处理图片列表间隙;
<style>
.wrap {
width: 340px;
}
.box {
overflow: hidden; /* BFC 清浮动*/
background-color: aquamarine;
}
.img {
float: left;
width: 100px;
height: 100px;
margin-right: 20px;
background-color: greenyellow;
}
</style>
<div class="wrap">
<div class="box">
<div class="img"></div>
<div class="img"></div>
<div class="img"></div>
</div>
</div>
wrap 宽 340px,box 继承 wrap 的宽,也为340px,img 盒子宽 100px 右外边距为 20px,3个 img 需要 (100 + 20)* 3 = 360px > 340px, 所以放不下去,3 号盒子换行了。
解决方法:
/*方法1:*/
.img:last-child {
margin: 0;
}
/*方法2:*/
.wrap {
width: 340px;
overflow: hidden;
}
.box {
overflow: hidden;
background-color: aquamarine;
margin-right: -20px;
}
box 的包含块是 wrap,故 box 的总宽度等于 wrap 的 content 宽。
3 个 img 在一行需要 360px,即 box 的 width (content)需要 360px(绿色线的位置),但容器 wrap 只有 340px (红色线位置),box 的总宽度只能为 340px。
340 (实)= 360 (需) + ( -20px )
先假设 wrap 的宽大小跟随 box 大小变化,box width 为 360px (绿色线位置),满足 3 个 img 和各自的右外边距处于一行的条件,此时,wrap 的 margin-right 也处在绿色线位置。box 设置 margin-right: -20px; 根据
margin 负值,元素宽度不变,top,left 让元素向上向左移动位置,bottom、right
只是移动元素参考线,让相邻元素更接近该元素
可知 box 的宽不变,仍为 360px,只是将 box 的右外边距 margin-right 的参考线向左移动到红色系位置,wrap 也随之跟着移动到红色系位置,此时 wrap 刚好为 340px,设置 overflow:hidden;将黄色区域(第三个盒子的右外边距)隐藏,
box 盒模型示意图:
-
视觉欺骗伪等高
等高布局是指子元素在父元素中高度相等的布局方式。等高布局有伪等高和真等高两类实现方法,伪等高只是看上去等高而已,真等高是实实在在的等高。margin 等高布局属于伪等高。实现方法:
1. 所有子元素添加 padding-bottom: n px; margin-bottom: -n px; n 是一个较大的数字。二者存在顺序问题。
2. 父元素添加 overflow: hidden;
原理:
先添加 padding, 让子元素向下占位置;margin 负值,把子元素的 bottom 参考线回到了未加 padding 时的起始位置,不影响父元素的兄弟元素的占位。
父元素添加 overflow:hidden; 父元素的高度会根据子元素中最高的元素的高度作为自己的高度,对之前添加占位元素 padding 进行隐藏。left 的高度为 200 + 1000 = 1200 px,right 的高度为 300 + 1000 = 1300px
从下图子元素的边框可以看出,子元素并非等高,只是因为占位的 padding 和 背景色的视觉误导,感觉实现了等高布局。
<style>
.wrap {
overflow: hidden; /* critical code */
background-color: #aece8a;
}
.left, .right {
padding-bottom: 1000px; /* critical code */
margin-bottom: -1000px; /* critical code */
}
.left {
float: left;
width: 100px;
height: 200px;
background-color: aquamarine;
}
.right {
float: left;
width: 200px;
height: 300px;
background-color: thistle;
}
</style>
</head>
<body>
<div class="wrap">
<div class="left">1</div>
<div class="right">2</div>
</div>b
参考:1. margin 值的特殊性
2. margin 系列学习
3.为什么 margin: auto 可以让块元素水平居中