CSS 学习之正确看待 CSS 世界里的 margin 合并
一、什么是 margin 合并
块级元素的上外边距(margin-top)与下外边距(margin-bottom)有时会合并为单个外边距,这样的现象称为“margin 合并”。从此定义上,我们可以捕获两点重要的信息。
- 块级元素,但不包括浮动和绝对定位元素,尽管浮动和绝对定位可以让元素块状化。
- 只发生在垂直方向,需要注意的是,这种说法在不考虑 writing-mode 的情况下才是正确的,严格来讲,应该是只发生在和当前文档流方向的相垂直的方向上。由于默认文档流 是水平流,因此发生 margin 合并的就是垂直方向。
二、margin 合并的 3 种场景
margin 合并有以下 3 种场景。
(1) 相邻兄弟元素 margin 合并。这是 margin 合并中最常见、最基本的,例如:
p { margin: 1em 0; }
<p>第一行</p>
<p>第二行</p>
则第一行和第二行之间的间距还是 1em,因为第一行的 margin-bottom 和第二行的 margin-top 合并在一起了,并非上下相加。
(2) 父级和第一个/最后一个子元素。我们直接看例子,在默认状态下,下面 3 种设置是等 效的:
<div class="father"><div class="son" style="margin-top: 80px"></div>
</div><div class="father" style="margin-top: 80px"><div class="son"></div>
</div><div class="father" style="margin-top: 80px"><div class="son" style="margin-top: 80px"></div>
</div>
在实际开发的时候,给我们带来麻烦的多半就是这里的父子 margin 合并。 比方说,现在流行官网使用一张帅帅的大图,然后配上大大的网站标
题。由于这个标题一般在头图中间的某位置,因此,我们很自然会想到使用 margin-top 定位,然后问题就来了。因为发生了“奇怪”的事情,头图居然 掉下来了!针对此现象,我特意制作了一个实例。
<div class="container"><h2>CSS世界</h2>
</div>
.container {max-width: 1920px;height: 384px;background: url(cover.jpg) no-repeat center;
}
.container > h2 {font-size: 128px;margin-top: 100px;color: #fff;
}

问题产生的原因就是这里的父子 margin 合并。这里大家需要理清楚“合并”这个概念。 如果我们按照中文释义理解,应该必须有多个对象才能进行合并,否则根本就没有“合”这一说,确实如此。但是,这样理解也有可能会带来这样一个误区,即你要出点儿力,我要出点儿 力,才叫“合”,其实不然。放到我们这里,这个父子 margin 合并的案例上就是:父元素没有出一点力,子元素出了全部的力,然后最终的 margin 全部合到了父元素上。也就是虽然是在子元素上设置的 margin-top,但实际上就等同于在父元素上设置了 margin-top,我想这样大家就能理解为何头图会掉下来了吧。但是有一点需要注意,“等同于”并不是“就是”的意思,我们使用 getComputedStyle 方法获取父元素的 margin-top 值还是 CSS 属性中设置值, 并非 margin 合并的表现值。
那该如何阻止这里 margin 合并的发生呢?
对于 margin-top 合并,可以进行如下操作(满足一个条件即可):
- 父元素设置为块状格式化上下文元素;
- 父元素设置 border-top 值;
- 父元素设置 padding-top 值;
- 父元素和第一个子元素之间添加内联元素进行分隔。
对于 margin-bottom 合并,可以进行如下操作(满足一个条件即可):
-
父元素设置为块状格式化上下文元素;
-
父元素设置 border-bottom 值;
-
父元素设置 padding-bottom 值;
-
父元素和最后一个子元素之间添加内联元素进行分隔;
-
父元素设置 height、min-height 或 max-height。
所以,上面因为 margin 合并导致头图掉下来的问题可以添加下面的 CSS 代码进行
修复:
.container {overflow: hidden;}
其原理就是通过设置 overflow 属性让父级元素块状格式化上下文,这在 6.4 节会有深入的 探讨。
说到此处,忍不住再多说几句。jQuery 中有个 ( ) . s l i d e U p ( ) / ().slideUp()/ ().slideUp()/().slideDown()方法, 如果在使用这个动画效果的时候,发现这内容在动画开始或结束的时候会跳一下,那八九不离十就是布局存在margin合并。跳动之所以产生,就是因为jQuery的slideUp和slideDown 方法在执行的时候会被对象元素添加 overflow:hidden 设置,而 overflow: hidden 会阻 止 margin 合并,于是一瞬间间距变大,产生了跳动。
(3) 空块级元素的 margin 合并。例如,下面 CSS 和 HTML 代码:
.father { overflow: hidden; }
.son { margin: 1em 0; }
<div class="father"><div class="son"></div>
</div>
结果,此时.father 所在的这个父级<div>元素高度仅仅是 1em,因为.son 这个空<div>元 素的 margin-top 和 margin-bottom 合并在一起了。这也是上一节 margin:50%最终宽高 比是 2:1 的原因,因为垂直方向的上下 margin 值合二为一了,所以垂直方向的外部尺寸只有 水平方向的一半。
这种空块级元素的 margin 合并特性即使自身没有设置 margin 也是会发生的,所谓“合” 并不一定要自己出力,只要出人就可以。比方说,我们一开始的“相邻兄弟元素 margin 合并”, 其实,就算兄弟不相邻,也是可以发生合并的,前提是中间插手的也是个会合并的家伙。比方说:
p { margin: 1em 0; }
<p>第一行</p>
<div></div>
<p>第二行</p>
此时第一行和第二行之间的距离还是 1em,中间看上去隔了一个
<div>和第一行 <p> 的 margin-bottom 合并,然后和第二行 <p>的 margin-top 合并,这两次合并是相邻兄弟合并。由于自身是空 <div>,于是前两次合并的 margin-bottom 和 margin-top 再次合并, 这次合并是空块级元素合并,于是最终间距还是 1em。
根据我多年开发的经验,由于空块级元素的 margin 合并发生不愉快事情的情况非常之少。 一来,我们很少会在页面上放置没什么用的空<div>;二来,即使使用空<div>也是画画分隔 线之类的,一般都是使用 border 属性,正好可以阻断 margin 合并;三来,CSS 开发人员普遍没有 margin 上下同时开工的习惯,比方说一个列表,间距都是一样的,开发人员一般都是单独设定 margin-top 或 margin-bottom 值,因为这会让他们内心觉得更安全。于是,最终,空块级元素的 margin 合并就变成了一个对 CSS 世界有着具有巨大意义但大多数人都不知道的特性。
如果有人不希望空<div>元素有 margin 合并,可以进行如下操作:
- 设置垂直方向的 border;
- 设置垂直方向的 padding;
- 里面添加内联元素(直接 Space 键空格是没用的);
- 设置 height 或者 min-height。
三、margin 合并的计算规则
我把 margin 合并的计算规则总结为“正正取大值”“正负值相加”“负负最负值”3 句话。
下面来分别举例说明。
(1) 正正取大值。如果是相邻兄弟合并:
.a { margin-bottom: 50px; }
.b { margin-top: 20px; }
<div class="a"></a>
<div class="b"></a>
此时.a 和.b 两个<div>之间的间距是 50px,取大的那个值。 如果是父子合并:
.father { margin-top: 20px; }
.son { margin-top: 50px; }
<div class="father"><div class="son"></div>
</div>
此时.father 元素等同于设置了 margin-top:50px,取大的那个值。 如果是自身合并:
.a {margin-top: 20px;margin-bottom: 50px;
}
<div class="a"></div>
则此时.a 元素的外部尺寸是 50px,取大的那个值。
(2)正负值相加。如果是相邻兄弟合并:
.a { margin-bottom: 50px; }
.b { margin-top: -20px; }
<div class="a"></a>
<div class="b"></a>
此时.a 和.b 两个<div>之间的间距是 30px,是-20px+50px 的计算值。 如果是父子合并:
.father { margin-top: -20px; }
.son { margin-top: 50px; }
<div class="father"><div class="son"></div>
</div>
此时.father 元素等同于设置了 margin-top:30px,是-20px+50px 的计算值。 如果是自身合并:
.a {margin-top: -20px;margin-bottom: 50px;
}
<div class="a"></div>
则此时.a 元素的外部尺寸是 30px,是-20px+50px 的计算值。
(3) 负负最负值。如果是相邻兄弟合并:
.a { margin-bottom: -50px; }
.b { margin-top: -20px; }
<div class="a"></a>
<div class="b"></a>
此时.a 和.b 两个<div>之间的间距是-50px,取绝对负值最大的值。 如果是父子合并:
.father { margin-top: -20px; }
.son { margin-top: -50px; }
<div class="father"><div class="son"></div>
</div>
此时.father 元素等同于设置了 margin-top:-50px,取绝对负值最大的值。
如果是自身合并:
.a {margin-top: -20px;margin-bottom: -50px;
}
<div class="a"></div>
则此时.a 元素的外部尺寸是-50px,取绝对负值最大的值。
四、margin 合并的意义
我之前曾见到类似这样的说法:“margin-top 合并 bug。”这种说法是大有问题的,
“margin-top 合并”这种特性是故意这么设计的,在实际内容呈现的时候是有着重要意义的, 根本就不是 bug!不要遇到出乎自己意料或者自己无法理解的现象就称其为 bug。
CSS世界的CSS属性是为了更好地进行图文信息展示而设计的,博客文章或者新闻信息是图文信息的典型代表,基本上离不开下面这些 HTML:
<h2>文章标题</h2>
<p>文章段落 1...</p>
<p>文章段落 2...</p>
<ul><li>列表 1</li><li>列表 2</li><li>列表 3</li>
</ul>
而这里的<h2>、<p>、<ul>默认全部都是有垂直方向的 margin 值的,而且单位全部都是 em。 首先解释一下为何需要 margin 值。其实原因很简单,CSS 世界的设计本意就是图文信息展示, 有了默认的 margin 值,我们的文章、新闻就不会挤在一起,垂直方向就会层次分明、段落有致,阅读体验就会好!为何使用 em 作为单位也很好理解,大家应该知道浏览器默认的字号大小是可以自定义的吧,例如,默认的是 16 像素,假如我们设置成更大号的字号,同时 HTML 标签的 margin 是像素大小,则会发生文字变大但是间距不变的情况,原本段落有致的阅读体验必然又会变得令人窒息。em 作为相对单位,则可以让我们的文章或新闻无论多大的字体都排版良好。可以看到,HTML 标签默认内置的 CSS 属性值完全就是为了更好地进行图文信息展示 而设计的。
我们平时进行网站开发的时候都会重置各种默认的 margin 尺寸,这是件需要好好审视的事情,对于绝大多数网站,确实需要做这样的处理,因为这些网站鲜有传统的图文信息展示区域。但是,如果你的站点是博客、新闻门户或公众号文章,我们应该做的是统一标签的 margin 大小,而不是一股脑地重置成 0。
下面说说 margin 合并的意义。对于兄弟元素的 margin 合并其作用和 em 类似,都是让图文信息的排版更加舒服自然。假如说没有 margin 合并这种说法,那么连续段落或列表之类首尾项间距会和其他兄弟标签成 1:2 关系;文章标题距离顶部会很近,而和下面的文章详情内容距离又会很开,就会造成内容上下间距不一致的情况。这些都是糟糕的排版体验。而合并机制可以保证元素上下间距一致,无论是<h2>标题这种 margin 偏大的元素,还是中规中矩的<p> 元素,因为“正正取大值”。
父子 margin 合并的意义在于:在页面中任何地方嵌套或直接放入任何裸<div>,都不会 影响原来的块状布局。<div>是网页布局中非常常用的一个元素,其语义是没有语义,也就是不代表任何特定类型的内容,是一个通用型的具有流体特性的容器,可以用来分组或分隔。由于其作用就是分组的,因此,从行为表现上来看,一个纯粹的<div>元素是不能够也不可以影响原先的布局的。现在有如下一段 HTML:
<div style="margin-top:20px;"></div>
请问:现在要在上面这段 HTML 的外面再嵌套一层<div>元素,假如说现在没有父子 margin 合并,那这层新嵌套的<div>岂不阻断了原本的兄弟 margin 合并?很有可能间距就会变大, 妥妥地影响了原来的布局,这显然就违背了<div>的设计作用了。所以才有了父子 margin 合并,外面再嵌套一层<div>元素就跟没嵌套一样,表现为 margin-top:20px 就好像是设置在最外面的<div>元素上一样。
自身 margin 合并的意义在于可以避免不小心遗落或者生成的空标签影响排版和布局。 例如:
<p>第一行</p>
<p></p>
<p></p>
<p></p>
<p></p>
<p>第二行</p>
其和下面这段 HTML 最终视觉效果是一模一样的:
<p>第一行</p>
<p>第二行</p>
若是没有自身 margin 合并特性的话,怕是上面的 HTML 第一行和第二行之间要隔了很多行吧。
知道了 margin 合并的意义以及作用,而且合并规则的兼容性良好,所以,我自己平时网页制作的时候,遇到列表或者模块,全部都是保留上下 margin 设置。例如:
.list {margin-top: 15px;margin-bottom: 15px;
}
而不是战战兢兢地使用:
.list {margin-top: 15px;
}
因为 margin 合并特性,所以我们无须担心列表之间的间距会很大。不会的,就是 15px! 相反,这种设置让我们的页面结构容错性更强了,比方说最后一个元素移除或位置调换,均不会破坏原来的布局,也就是我们的 CSS 无须做任何调整。
参考资料: 《CSS 世界》
相关文章:
CSS 学习之正确看待 CSS 世界里的 margin 合并
一、什么是 margin 合并 块级元素的上外边距(margin-top)与下外边距(margin-bottom)有时会合并为单个外边距,这样的现象称为“margin 合并”。从此定义上,我们可以捕获两点重要的信息。 块级元素,但不包括浮动和绝对定位元素,尽…...
杰发科技——使用ATCLinkTool解除读保护
0. 原因 在jlink供电电压不稳定的情况下,概率性出现读保护问题,量产时候可以通过离线烧录工具避免。代码中开了读保护,但是没有通过can/uart/lin/gpio控制等方式进行关闭,导致无法关闭读保护。杰发所有芯片都可以用本方式解除读保…...
uni-app深度解码:跨平台APP开发的核心引擎与创新实践
在当今数字化浪潮中,移动应用市场呈现出爆炸式增长。为了满足不同用户群体在不同操作系统上的需求,跨平台 APP 开发成为众多开发者的首选策略。uni-app 作为一款领先的跨平台开发框架,以其独特的优势和创新的实践在众多同类产品中脱颖而出。它…...
unity团结云下载项目
今天开plastic scm发现它云服务好像停了哈,在hub里下载云端项目也不会出现在项目列表里,之前也有发邮件说让提前迁移到团结云。打开云仓库会弹这个,大概就是plastic scm无法解析域名地址吧 研究了一下团结云咋使,官方手册看半天也…...
Jmeter进阶篇(31)解决java.net.BindException: Address already in use: connect报错
📚前言 近期雪雪妹妹在使用Jmeter执行压测的时候,发现了一个非常让她头疼的问题,她使用20并发跑,正确率可以达到100%,但是一旦使用200并发,就会出现大量的报错,报错内容如下: java.net.BindException: Address already in use: connectat java.net.DualStackPlainSo…...
商米电子秤服务插件
概述 SunmiScaleUTS封装商米电子秤服务模块,支持商米旗下S2, S2CC, S2L CC等设备,设备应用于超市、菜市场、水果店等,用于测量商品的重量,帮助实现快捷、准确、公正的交易等一系列商业场景。 功能说明 SDK插件下载 一. 电子秤参数 型号:S2, S2CC, …...
华为ensp-BGP路由过滤
学习新思想,争做新青年,今天学习的是BGP路由过滤 实验目的: 掌握利用BGP路由属性AS_Path进行路由过滤的方法 掌握利用BGP路由属性Community进行路由过滤的方法 掌握利用BGP路由属性Next_Hop进行路由过滤的方法 实验内容: 本实…...
Sigrity System SI SerialLink模式进行Pcie3协议仿真分析操作指导-pcie3_client_single_post
Sigrity System SI SerialLink模式进行Pcie3协议仿真分析操作指导-pcie3_client_single_post Sigrity System SI SerialLink模式提供了10个协议合规性检查工具模板,用户可以将根据实际应用替换模板中的SPICE文件,然后进行协议仿真分析,同时软件还提供了目标结果的模板MASK以…...
Python提取目标Json键值:包含子嵌套列表和字典
目标:取json中所有的Name、Age字典 思路:递归处理字典中直接包含子字典的情况, import jsondef find_targ_dicts(data,key1,key2):result {}if isinstance(data, dict):if key1 in data and key2 in data: # 第一层字典中包含key1和key2re…...
分享6个对象数组去重的方法
大家好,关于对象数组去重的业务场景,想必大家都遇到过类似的需求吧,针对这样的需求,你是怎么做的呢。 下面我就先和大家讨论下基于对象的某个属性如何去重。 方法一:使用 .filter() 和 .findIndex() 相结合的方法 使…...
Formality:官方Tutorial(一)
相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 本文是对Synopsys Formality User Guide Tutorial中第一个实验的翻译(有删改),Lab文件可以从以下链接获取。 Formality官方Tu…...
力扣28找出字符串中第一个匹配项的下标
class Solution:def strStr(self, haystack: str, needle: str) -> int:# 特殊情况处理if not needle:return 0# 获取 haystack 和 needle 的长度a len(needle)b len(haystack)# 遍历 haystack,检查每个子字符串是否与 needle 匹配for i in range(b - a 1):if…...
【JAVA】java中将一个list进行拆解重新组装
一、使用场景 1、当需要对一个list中的元素属性进行重新赋值,比如一个list中存储了订单数据,我们需要改变list中每个订单的id,然后再重新输出订单list if(CollectionUtils.isNotEmpty(orderList)){ orderList.forEach(p->{ …...
在 Windows 上使用 SSH 密钥访问 Linux 服务器
本章目录: 前言1. 准备工作2. 生成 SSH 密钥对步骤 1:打开命令行步骤 2:运行 ssh-keygen 命令步骤 3:选择密钥保存位置步骤 4:设置密钥密码(可选)步骤 5:生成密钥对 3. 查看生成的密钥文件4. 将…...
小白0基础centos8安装docker
总述:博主作为0基础小白将详细记录第一次从centos8的下载到安装docker的过程,包括中间出现的问题和解决方案 1Centos8下载 参见博文CentOS 8 的安装(官方安装、清华大学开源软件镜像站、阿里云镜像、网易镜像下的安装步骤)_cent…...
机器学习之逻辑回归算法、数据标准化处理及数据预测和数据的分类结果报告
逻辑回归算法、数据标准化处理及数据预测和数据的分类结果报告 目录 逻辑回归算法、数据标准化处理及数据预测和数据的分类结果报告1 逻辑回归算法1.1 概念理解1.2 算法导入1.3 算法优缺点 2 LogisticRegression理解2.1查看参数定义2.2 参数理解2.3 方法2.4基本格式 3 数据标准…...
openGauss连接是报org.opengauss.util.PSQLException: 尝试连线已失败
安装好高斯数据库后然后用java连接时报如下错误: 解决方法: 在openGauss数据库的安装路径下/opt/opengauss/data/single_node(这个路径根据自己实际情况变化)有个pg_hba.conf文件,修改里面host内容如下,我这里设置的是所有ip都能…...
详细的一条SQL语句的执行流程
SQL 语句的执行流程会因数据库管理系统的不同而略有差异,但一般来说,主要包括以下几个阶段: 查询解析 词法分析:数据库系统首先将输入的 SQL 语句按字符流进行扫描,依据词法规则把它分割成一个个的单词,如…...
适用于小白的程序报错提问 AI 模板
#工作记录 程序报错提问 AI 模板 1、你现在将扮演python专家,请保持连续对话,请基于你的专业知识修改代码并回答! 2、可以向我询问任何有利于你分析问题的信息。 3、你修改的程序代码,运行后报错,报错信息我放在最后…...
web实操9——session
概念 数据保存在服务器HttpSession对象里。 session也是域对象,有setAttribute和getAttribute方法 快速入门 代码 获取session和塞入数据: 获取session获取数据: 请求存储: 请求获取: 数据正常打印:…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
