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获取数据: 请求存储: 请求获取: 数据正常打印:…...

OFDM学习-(二)长短序列和PPDU整体数据处理流程
OFDM学习 (二)长短序列和PPDU整体数据处理流程 OFDM学习前言一、短序列短序列的作用 二、长序列三、PLCP/SIGNAL/DATA数据处理流程三、fpga实现STS模块LTS模块训练序列模块仿真波形 总结 前言 根据框图可以知道发射机这部分信号在DA转换之前,…...

.NET周刊【12月第4期 2024-12-22】
国内文章 dotnet 简单使用 ICU 库进行分词和分行 https://www.cnblogs.com/lindexi/p/18622917 本文将和大家介绍如何使用 ICU 库进行文本的分词和分行。 dotnet 简单聊聊 Skia 里的 SKFontMetrics 的各项属性作用 https://www.cnblogs.com/lindexi/p/18621674 本文将和大…...

Conda 安装 Jupyter Notebook
文章目录 1. 安装 Conda下载与安装步骤: 2. 创建虚拟环境3. 安装 Jupyter Notebook4. 启动 Jupyter Notebook5. 安装扩展功能(可选)6. 更新与维护7. 总结 Jupyter Notebook 是一款非常流行的交互式开发工具,尤其适合数据科学、机器…...

鸿蒙开发:实现键值数据库存储
前言 鸿蒙当中数据持久化存储,为我们提供了多种的实现方式,比如用户首选项方式,关系型数据库方式,键值型数据库方式,文件存储方式等等,对于数据量比较的小的,我们直接选择轻量级的用户首选项方式…...

WebGL 实践(一)开发环境搭建
WebGL 是一种基于 JavaScript 的 API,用于在浏览器中渲染 2D和3D 图形,很多场景都能用,例如游戏开发、数据可视化、在线教育和虚拟现实等应用领域。 零、基础知识 相关基础知识很重要,如果会html、JavaScript等技术掌握起来会更…...

在线机考|2024华为实习秋招春招编程题(最新)——第3题_PCB印刷电路板布线_300分(八)
题目内容 在PCB印刷电路板设计中,器件之间的连线需要避免线路的阻抗值增大、而且赛件之间还有别的器件和别的干扰源,在布线时我们希望受到的干扰尽量小。现将电路板简化成一个MN的矩阵,每个位置(单元格)的值表示其源干扰度。 如果单元格的值为0,表示此位置没有干扰源;如果单…...

Spring源码分析之事件机制——观察者模式(三)
目录 自定义事件 事件监听器 事件发布者(服务层) 使用示例controller层 Spring源码分析之事件机制——观察者模式(一)-CSDN博客 Spring源码分析之事件机制——观察者模式(二)-CSDN博客 这两篇文章是这…...

C#实现画图,及实现图像运动,C#中GDI+图形图像技术(Graphics类、Pen类、Brush类)C#之快速入门GDI+绘图 C#实现快速画图功能
下载源码 <-------- 在C#的世界里,GDI如同一位多才多艺的艺术家,以其强大的绘图能力,让开发者能够轻松地在应用程序中挥洒创意,绘制出丰富多彩的图形世界。GDI不仅支持基本的几何图形绘制,还能处理复杂的图像处理任…...

【51单片机零基础-chapter6:LCD1602调试工具】
实验0-用显示屏LCD验证自己的猜想 如同c的cout,前端的console.log() #include <REGX52.H> #include <INTRINS.H> #include "LCD1602.h" int var0; void main() {LCD_Init();LCD_ShowNum(1,1,var211,5);while(1){;} }实验1-编写LCD1602液晶显示屏驱动函…...

算法题(24):只出现一次的数字(二)
审题: 数组中除了答案元素只出现一次外,其他元素都会出现三次,我们需要找到并返回答案元素 思路: 由于现在会出现三次,所以利用异或运算符的方法就会失效。而所有数据都在32位二进制范围内,所以我们采用依次…...