前端知识库(lizh)
  • README
  • Bugs
    • 前端调试随笔
    • 浏览器常见问题概览
    • 浏览器兼容问题概览
    • HTML常见问题概览
    • CSS常见问题概览
    • JS常见问题概览
    • 移动端兼容性问题概览
    • 微信小程序开发
    • NodeJs常见问题概览
    • Mac常见问题概览
    • 微信开发遇坑指南
    • Npm包常见问题概览
    • 其他问题汇总
  • Css探索系列
    • CSS基础知识
    • CSS常见问答
    • CSS常见问答02
    • CSS应用示例
    • CSS应用示例02
    • 由Z Index引发的层叠上下文思考
    • 由浮动塌陷引发的块级格式上下文思考
    • CSS探索系列 Flex布局
    • CSS探索系列 Margin
    • CSS探索系列 Auto关键字
    • CSS探索系列 Gradient
    • CSS探索系列 Line Height
    • CSS探索系列 元素居中
    • CSS探索系列 动画
    • 为什么使用PostCSS处理CSS?
    • 重新认识伪类与伪元素
    • 自定义表单伪元素样式
    • 如何理解Css中的Display属性
    • 视口和软键盘对视口的影响
    • 关于Css
  • Frontend
    • 00 关于Web前端
    • 01 前端知识概览
    • 02 常用前端库概览
    • 基础 00 前端常见问题01
    • 基础 01 浏览器缓存
    • 基础 02 浏览器工作原理
    • 基础 03 谈谈前端跨源问题及解决方法
    • 进阶 01 Web性能优化
    • 进阶 02 搜索引擎优化(SEO)
    • 进阶 03 前端模块化编程
    • 进阶 04 规范代码:Linter、Prettier、EditorConfig
    • 进阶 11 前端自动化测试
    • 高级 01 前端安全
    • Vue2.X原理篇
    • Vue3初步了解及迁移指南
    • 重读Vue教程
    • React17.X原理篇
    • 你必须知道的React问题
    • 重读React教程
    • 聊一聊Cookie的一些问题
    • 如何理解HTTP响应的状态码
    • HTTP的历史演变及概述
    • Webpack4.X原理篇
    • Webpack基础入门篇
    • Webpack构建优化篇
    • TypeScript使用指南
    • 代码规范
      • 前端规范
      • HTML
      • CSS
      • JS
  • Html探索系列
    • HTML基础知识
    • HTML基础知识02
    • HTML常见问答
    • HTML经典实践用例
    • HTML元素的宽高及位置详解
    • Video元素的使用和常见问题总结
    • Html探索系列 Meta标签
    • DOCTYPE:文档类型与浏览器模式
    • DHTML(动态网页)简介
    • HTML标签详解
    • HTML布局的几种方式
    • HTML全局属性
    • 关于Html
  • Js探索系列
    • 基础知识
    • 常见问答
    • 应用示例
    • 趣味示例
    • 基础篇 05 AJAX
    • 基础篇 06 Window对象
    • 基础篇 07 Error、JSON、Math、Console对象
    • 基础篇 08 History、URL、Screen、Navigator、Location对象
    • 基础篇 09 文档对象模型(DOM)
    • 基础篇 10 Document对象
    • 基础篇 11 Element对象
    • 基础篇 12 Event对象
    • 基础篇 13 键盘、鼠标、触摸事件
    • 基础篇 15 CSS对象模型(CSSOM)
    • 进阶篇 01 Prototype对象和继承
    • 进阶篇 02 Promise对象
    • 进阶篇 07 迭代器(Iterator)
    • 进阶篇 08 Generator和Async函数
    • 进阶篇 09 JavaScript异步编程
    • Date对象和日期时间字符串格式
    • Canvas基础入门篇
    • Canvas进阶篇
    • SVG基础入门篇
    • 四种判断数据类型方法的优缺点
    • 深入理解JavaScript的浅拷贝和深拷贝
    • 谈谈JavaScript的作用域和上下文
    • 复制内容到剪贴板
    • 关于Javascript
  • NodeJs
    • 关于Node.Js
    • Node.Js:三种调试方法
    • Npm包管理器简介及一些机制
    • NPM:Package.Json详解(中文)
    • NPM:从零开始,开发一个软件包
    • NPM:常用命令
    • Node.Js:Fs(文件系统)
    • Node.Js:Global(全局变量)
    • Node.Js:HTTP
    • Node.Js:Module(模块)
    • Node.Js:Path(路径)
    • Node.Js:Readline(逐行读取)
  • Research
    • 极细边框(1px边框)实现方式
    • 如何监控前端异常?
    • H5页面跳转和刷新
    • Web主题切换和个性化定制方法总结
    • Vue SSR(服务端渲染)的简单实现
    • 基于Create React App打造代码规范化的React+Ts项目
    • H5可视化编辑
    • Web常用功能
    • Javascript加密混淆
    • Vue如何导入TypeScript
    • 移动端PDF预览
    • 纯CSS绘制箭头
    • 网站性能测量和优化方法
  • Tech
    • GOOGLE浏览器的搜索技巧
    • Curl的用法指南
    • Sublime3插件篇
    • Charles安装及使用
    • Nginx基础使用
    • 排序算法(Javascript)
    • 代码整洁之道(摘录笔记)
    • Java的24种设计模式与7大原则
    • 观察者和发布订阅模式
  • Tools
    • Git
      • Git基础教程
      • Git常见问题
    • Gitbook
      • Gitbook入门篇
      • Gitbook插件篇
      • Gitbook进阶篇
由 GitBook 提供支持
在本页
  • Margin是什么?
  • 神奇的外边距折叠
  • Why?
  • 到底什么是Margin折叠?
  • 如何避免margin折叠?
  • 怪异的百分比
  • 你可能从没注意过它
  • 为什么会这样?
  • 为什么参照的是width,而不是height?
  • Margin: Auto
  • Margin与Relative的异同
  • Margin应用示例
  • 等高布局
  • 去掉列表最后一项下边框
  • 圣杯布局
  • 双飞翼布局
  • 书写模式对Margin的影响
  • 常见问题
  • 参考链接

这有帮助吗?

  1. Css探索系列

CSS探索系列 Margin

Margin是什么?

CSS 边距属性定义元素周围的空间。通过使用单独的属性,可以对上、右、下、左的外边距进行设置。也可以使用简写的外边距属性同时改变所有的外边距。

——W3School

边界,元素周围生成额外的空白区。“空白区”通常是指其他元素不能出现且父元素背景可见的区域。

——CSS 权威指南

Margin 常被用于:

  • 块元素水平居中;

  • 元素之间留有舒适的留白;

  • 处理特殊的 first 或 last 元素;

  • 一些布局,如:等高布局;

需要注意的地方:

  • 神奇的外边距折叠;

  • 怪异的百分比值;

  • auto 值;

  • 与相对偏移属性(top、right、bottom、left)的异同;

神奇的外边距折叠

<div class="wrap">
    <p>我喜欢回忆,是因为回忆是我们审视自己生活的过滤器。</p>
    <p>回忆模糊不清,就给自我欺骗提供了机会。</p>
</div>
<style type="text/css">
    .wrap {
        border: 1px solid #f00;
    }
    .wrap p {
        margin: 50px 0;
    }
</style>

按照常规思路,这两个 <p> 之间的空白应该是第一个 margin-bottom 50px 加上第二的 margin-top 50px,即 50 + 50px = 100px ,但结果总是出乎意料不是么?

它们之间只剩下了 50px。

Why?

这就是Margin的外边距折叠现象。

早在CSS1中就有对 margin 折叠的说明,我们来看看:

原文:The width of the margin on non-floating block-level elements specifies the minimum distance to the edges of surrounding boxes. Two or more adjoining vertical margins (i.e., with no border, padding or content between them) are collapsed to use the maximum of the margin values. In most cases, after collapsing the vertical margins the result is visually more pleasing and closer to what the designer expects.

翻译:外边距用来指定非浮动元素与其周围盒子边缘的最小距离。两个或两个以上的相邻的垂直外边距会被折叠并使用它们之间最大的那个外边距值。多数情况下,折叠垂直外边距可以在视觉上显得更美观,也更贴近设计师的预期。

从这段话中,我们能获得一些有用的信息:

  • 发生折叠需要是相邻的非浮动元素。

  • 折叠发生在垂直外边距上,即margin-top/margin-bottom。

    注:此特性受书写模式影响,具体可看本文后面章节。

  • 折叠后取其中最大的那个margin值作为最终值。

到底什么是Margin折叠?

在CSS中,两个或以上的块元素(可能是兄弟,也可能不是)之间的相邻外边距可以被合并成一个单独的外边距。通过此方式合并的外边距被称为折叠,且产生的已合并的外边距被称为折叠外边距。

处于同一个块级上下文中的块元素,没有行框、没有间隙、没有内边距和边框隔开它们,这样的元素垂直边缘毗邻,则称之为相邻。

什么是垂直边缘毗邻?

而根据 w3c 规范,两个 margin 是邻接的必须满足以下条件:

  • 必须是处于常规文档流(非 float 和绝对定位)的块级盒子,并且处于同一个 BFC 当中;

  • 没有 padding 和 border 将他们分隔开;

  • 属于垂直方向上相邻的外边距,可以是下面任意一种情况:

    • 元素的 margin-top 与其第一个常规文档流的子元素的 margin-top;

    • 元素的 margin-bottom 与其下一个常规文档流的兄弟元素的 margin-top;

    • height 属性值为 auto 的元素的 margin-bottom 与其最后一个常规文档流的子元素的 margin-bottom;

    • min-height 的值为零、height 的值为零或 auto,不包含常规文档流的子元素,并且自身没有建立新的 BFC 的元素的margin-top 和 margin-bottom。

你可能需要注意的是发生 margin 折叠的元素不一定是兄弟关系,也能是父子或祖先的关系。

如何避免margin折叠?

避免 margin 折叠,我们可以从 margin 折叠需要满足的条件着手:

  • 为元素创建块级格式上下文元素(BFC),使两个元素不属性同一 BFC,比如:position: absolute 、float: left | right、display: inline-block;

  • 为元素设置 padding、border 属性;

  • 根元素的 margin 不与其它任何 margin 发生折叠;

  • 元素设置 height、min-height、max-height,可避免与最后一个子元素折叠;

  • 元素设置 height、min-height,可避免空元素折叠;

怪异的百分比

你可能从没注意过它

我必须承认这是一个非常基础的知识点,但仍然有很多人可能对此有错误的认知。

假设一个块级包含容器,宽600px,高300px,块级子元素定义 margin:10% 5%; 大家说说 margin 的 top, right, bottom, left 计算值最终是多少?

应该有不少人脱口而出: 60px 15px 60px 15px 。

<div class="wrap">
    <p>我喜欢回忆,是因为回忆是我们审视自己生活的过滤器。</p>
</div>
<style type="text/css">
    .wrap {
      width: 600px;
      height: 300px;
      border: 1px solid #f00;
    }
    .wrap p {
        margin: 10% 5%;
        background: #0aa;
    }
</style>

事实告诉我们结果是 60px 30px 60px 30px。

为什么会这样?

诧异吗?不用怀疑浏览器出了问题,因为这是正确的实现。

规范中注明 margin 的百分比值参照其包含块的宽度进行计算。

Note that in a horizontal flow, percentages on ‘margin-top’ and ‘margin-bottom’ are relative to the width of the containing block, not the height (and in vertical flow, ‘margin-left’ and ‘margin-right’ are relative to the height, not the width).
Note that percentages on ‘padding-top’ and ‘padding-bottom’ are relative to the width of the containing block, not the height (at least in a horizontal flow; in a vertical flow they are relative to the height).

当然,它不会这么简单,以上只是发生在默认的横向书写的情况下。

注:此特性受书写模式影响,具体可看本文后面章节。

为什么参照的是width,而不是height?

对于margin/padding-top/bottom 的百分比之所以按照 width 计算,目前主要有以下两种解释:

  • 相对于height计算会引起无线循环

    正常流中的大多数元素都会足够高以包含其后代元素(包括外边距),如果一个元素的上下外边距是父元素的height的百分数,就可能导致一个无限循环,父元素的height会增加,以适应后代元素上下外边距的增加,而相应的,上下外边距因为父元素height的增加也会增加,如此循环。

  • 基于排版的需求

    之所以按照同一个 width 来计算,是为了要横向和纵向2个方向都创建相同的 margin/padding,如果它们的参照物不一致,那用百分比就无法得到垂直和水平一致的留白。

Margin: Auto

auto 是 margin 的可选值之一。相信大家平时使用 auto 值时,最多的用法大概是 margin: 0 auto; 和 margin: auto;,恩,是的,就是块元素水平居中。不过你可能也发现了,不论是 margin: auto; 还是 margin: 0 auto; 效果都是一样的,都是让元素水平居中了,但纵向并没有任何变化。

根据规范,margin-top: auto; 和 margin-bottom: auto;,其计算值为0。这也就解释了为什么 margin: auto; 等同于 margin: 0 auto;。

为什么auto能实现水平居中?

这是因为水平方向的 auto,其计算值取决于可用空间(剩余空间)。

注:此特性受书写模式影响,具体可看本文后面章节。

想象这样一个场景:一个宽200px的元素被包含在一个宽600px的元素,此元素的 margin-left: auto,大家觉得结果会怎样?

<div class="wrap">
    <div class="margin_left">
      我喜欢回忆,是因为回忆是我们审视自己生活的过滤器。回忆模糊不清,就给自我欺骗提供了机会。—— 石黑一雄《远山淡影》
    </div>
</div>
<style type="text/css">
  .wrap {
    width: 600px;
    border: 1px solid #f00;
  }
  .wrap .margin_left {
    width: 200px;
    margin-left: auto;
    background: #0aa;
  }
</style>

这与规范描述一致。margin-left: auto; 自动占据了父节点的的可用空间,即 600px - 200px = 400px。而元素中没有 margin-right,也就是可用空间全部是 margin-left 占用的,最后的结果要当于 margin-left:400px;。即 margin-left:auto; 的结果会相当于右对齐。

同样,margin: auto; 和 margin: 0 auto; 能实现水平居中了。因为左右方向的 auto 值均分了可用空间,使得块元素得以在包含块内居中显示。

总结: margin 水平方向的属性值,若一侧定值,一侧值为 auto,则 auto为剩余空间大小;如果两侧均是 auto,则平分剩余空间。

Margin与Relative的异同

<div class="wrap">
  <div class="compare">
    <div class="margin">
      我喜欢回忆,是因为回忆是我们审视自己生活的过滤器。回忆模糊不清,就给自我欺骗提供了机会。—— 石黑一雄《远山淡影》
    </div>
  </div>
  <div class="compare">
    <div class="relative">
      我喜欢回忆,是因为回忆是我们审视自己生活的过滤器。回忆模糊不清,就给自我欺骗提供了机会。—— 石黑一雄《远山淡影》
    </div>
  </div>
</div>
<style type="text/css">
  .wrap {
    border: 1px solid #f00;
  }
  .wrap .compare {
    display: inline-block;
    width: 49%;
    padding: 10px;
    vertical-align: top;
    box-sizing: border-box;
  }
  .wrap .margin {
    margin-top: 50px;
    background: #a0a;
  }
  .wrap .relative {
    position: relative;
    top: 50px;
    background: #0aa;
  }
</style>

这似乎没什么不同?都完成了向下偏移 50px的需求。

好吧。这里还真没什么不同,至少在这里没有!不论是 margin-top 还是 relative top 都是以自身作为参照物进行偏移的。

但它们真的不一样!

我们来看看规范怎么说:

margin:

原文:Margins in CSS serve to add both horizontal and vertical space between boxes.

翻译:CSS中的margin用来添加盒子之间的水平和垂直间隙。

top, right, bottom, left:

原文:An element is said to be positioned if its ‘position’ property has a value other than ‘static’. Positioned elements generate positioned boxes, and may be laid out according to the following four physical properties: top, right, bottom, left.

翻译:一个元素的position属性值如果不为static则发生定位。定位元素会产生定位盒,并且会根据 top, right, bottom, left 这4个物理属性进行排版布局。

意思很明白,margin 是用来增加自身与他人之间的空白,而 top, right, bottom, left 是用来对自身进行排版,作用完全不同。

也就是说 margin 是互动的,因为它要影响他人;而 top, right, bottom, left是孤独的,他只是自己一个人玩,不影响他人。

不是很明白?!还是直接看图吧:

<div class="wrap">
  <div class="compare">
    <div class="margin">
      我喜欢回忆,是因为回忆是我们审视自己生活的过滤器。回忆模糊不清,就给自我欺骗提供了机会。—— 石黑一雄《远山淡影》
    </div>
    <div class="diff">我们不一样,每个人都有不同的境遇。。。</div>
  </div>
  <div class="compare">
    <div class="relative">
      我喜欢回忆,是因为回忆是我们审视自己生活的过滤器。回忆模糊不清,就给自我欺骗提供了机会。—— 石黑一雄《远山淡影》
    </div>
    <div class="diff">我们不一样,每个人都有不同的境遇。。。</div>
  </div>
</div>
<style type="text/css">
  .wrap {
    border: 1px solid #f00;
  }
  .wrap .compare {
    display: inline-block;
    width: 49%;
    padding: 10px;
    vertical-align: top;
    box-sizing: border-box;
  }
  .wrap .margin {
    margin-top: 50px;
    background: #a0a;
  }
  .wrap .relative {
    position: relative;
    top: 50px;
    background: #0aa;
  }
  .wrap .diff {
    position: relative;
    padding: 10px;
    color: #f00;
  }
</style>

结果有出乎你的意料吗?好吧,不论怎么,解释下为什么会这样?

上面说过,margin 是用来增加自身与他人之间的空白,是互动的,因为他要影响他人。即margin元素往下偏移了50px,他后面的兄弟元素是从他偏移后的位置开始排版;

而 top, right, bottom, left 是用来对自身进行排版,他只是自己一个人玩,不影响他人。即ralative元素虽然也往下偏移了50px,但他的兄弟元素,还是从他偏移前的位置开始排版。

简而言之,你眼睛看到的relative元素,并不是他在布局中的真正位置。

这才是他在布局中的位置:

Margin应用示例

等高布局

要求:不论是主栏还是侧栏,总是以最高的那列为基准高度。

<div class="wrap">
    <div class="main">但凡死缠烂打的人,大都不是真的深爱你,那只是在跟自己赛跑。真正爱你的人,做不到死缠烂打。因为自尊不允许。我们一直深信,爱就是把最好的一切给予对方,包括尊严。--多少浅浅淡淡的转身,是旁人看不懂的情深。—— 苏芩</div>
    <div class="aside">多少浅浅淡淡的转身,是旁人看不懂的情深</div>
</div>
<style type="text/css">
    .wrap { overflow: hidden; font-size: 0; }
    .main, .aside{
        display: inline-block;
        width: 50%;
        padding: 10px;
        font-size: initial;
        background: #a0a;
        vertical-align: top;

        margin-bottom: -999px;
        padding-bottom: 999px;
        box-sizing: border-box;
    }
    .aside { background: #0aa; }
</style>

去掉列表最后一项下边框

<div class="wrap">
    <ul>
        <li>多少浅浅淡淡的转身,是旁人看不懂的情深</li>
        <li>多少浅浅淡淡的转身,是旁人看不懂的情深</li>
        <li>多少浅浅淡淡的转身,是旁人看不懂的情深</li>
        <li>多少浅浅淡淡的转身,是旁人看不懂的情深</li>
        <li>多少浅浅淡淡的转身,是旁人看不懂的情深</li>
    </ul>
</div>
<style>
    .wrap ul { margin-bottom: -1px; }
    .wrap li{
        list-style: none;
        padding: 5px;
        border-bottom: 1px solid #a0a;

        overflow: hidden;
    }
</style>

这里只是简单了解下,实现的方法有很多,比如: :first-child、为最后一项单独写 class。

圣杯布局

在 CSS 中,圣杯布局是指两边盒子宽度固定,中间盒子自适应的三栏布局,其中,中间栏放到文档流前面,保证先行渲染;三栏全部使用 float: left,并配合 left、right 属性。

<header>header</header>
<div class="container">
    <!-- 这里把 middle 区域放在第一位是为了让浏览器先加载 middle 区域 -->
    <div class="middle">middle(中间弹性区)</div>
    <div class="left">left</div>
    <div class="right">right</div>
</div>
<footer>footer</footer>
header {
    height: 50px;
    background-color: red;
}
.container {
    height: 200px;
    overflow: hidden;
    padding: 0 200px;
}
.middle {
    width: 100%;
    height: 200px;
    background-color: #ECECEC;
    float: left;
}
.left {
    width: 200px;
    height: 200px;
    position: relative;
    left: -200px;
    margin-left: -100%;
    background-color: blue;
    float: left;
}
.right {
    width: 200px;
    height: 200px;
    position: relative;
    right: -200px;
    margin-left: -200px;
    background-color: yellow;
    float: left;
}
footer {
    height: 50px;
    background-color: black;
}

双飞翼布局

圣杯布局其实和双飞翼布局是一回事。它们实现的效果是一样的,差别在于其实现的思想。

双飞翼布局在 middle 块子元素 div.in 中使用 padding 留出左右两边区域,让 left、right 块无需再用 reletive 定位:

...
<div class="container">
    <div class="middle">
        <div class="in">middle(中间弹性区)</div>
    </div>
    ...
</div>
...
.container {
    height: 200px;
    overflow: hidden;
}
.in {
    padding: 0 200px 0 200px;
}
.left {
    width: 200px;
    height: 200px;
    margin-left: -100%;
    background-color: blue;
    float: left;
}
.right {
    width: 200px;
    height: 200px;
    margin-left: -200px;
    background-color: yellow;
    float: left;
}

书写模式对Margin的影响

CSS中有一些样式是受书写模式影响的。非常不幸,margin 正好是其中之一。

  • 在水平书写模式下,发生 margin 折叠的是垂直方向,即 margin-top/margin-bottom;在垂直书写模式下,发生 margin 折叠是的水平方向,即 margin-right/margin-left。

  • 在水平书写模式下, margin/padding-* 的百分比都按照width计算。在垂直书写模式下, margin/padding-* 都按照 height计算。

  • 在水平书写模式下, margin: auto; 等同于 margin: 0 auto; ,即top、bottom为0;在垂直书写模式下, margin: auto; 等同于 margin: auto 0; ,即left、right为0。

可用 write-mode 更改书写模式,属性值:

/* 关键值 */ 
horizontal-tb:水平方向自上而下的书写方式。即 left-right-top-bottom
vertical-rl:垂直方向自右而左的书写方式。即 top-bottom-right-left
vertical-lr:垂直方向内内容从上到下,水平方向从左到右
sideways-rl:内容垂直方向从上到下排列
sideways-lr:内容垂直方向从下到上排列
/*除火狐浏览器,其他的浏览器都不支持 sideways-*/

在水平书写模式(默认):

/*块级方向:由上到下;行内方向:水平方向,从左至右。 */
.h {
    writing-mode: horizontal-tb;
    direction: ltr;
}

在垂直书写模式:

.v {
	writing-mode: vertical-rl; 
}

direction :用来设置文本、表列水平溢出的方向。 rtl 表示从右到左 (类似希伯来语或阿拉伯语), ltr 表示从左到右 (类似英语等大部分语言)。

常见问题

哪些情况下 margin 会无效?

因为 margin 属性的诸多特异性,所以,在实际开发时,经常会遇到设置的 margin 无效的情形:

  • margin 折叠时: 垂直方向的 margin 折叠的时候,更改 margin 值可能会没有效果的。

  • inline 元素: display: inline 的非替换元素的垂直 margin 是无效的,虽然规范提到有渲染,但浏览器表现却未寻得一点踪迹;对于内联替换元素,垂直 margin 有效,并且没有 margin 折叠的问题;

  • 表格: 表格中的 tr、td 元素或者设置 display: table-cell|table-row 的元素的 margin 都是无效的。但是,如果是 display: table-caption|table|inline-table 则可以通过 margin 控制外间距,甚至 ::first-letter 伪元素也可以解析 margin。

  • 绝对定位元素非定位方位的 margin: 比如,元素绝对定位时,只设置 top: 10%; left: 30%; 两个方位,则 right|bottom 值属于 auto 状态,也就是右侧和底部没有进行定位,此时,这两个方向设置 margin 值是无效的。??

  • 定高或定宽容器的子元素: 定高容器的子元素的 margin-bottom 或者定宽容器的子元素的 margin-right 的定位无效的。这个时候并不是无效,只是给人的感觉是无效。

参考链接

上一页CSS探索系列 Flex布局下一页CSS探索系列 Auto关键字

最后更新于1年前

这有帮助吗?

image-20200504010557625
image-20200504012208342
image-20200503234104813
image-20200503225626449
image-20200503225830177
image-20200503230100757
image-20200504014912231
image-20200504015238324

CSS 书写模式的24种方式
margin系列文章
css进阶之十三:margin合并与margin无效