编辑注: 正在寻找一种更现代的方法来在元素之间获得一致的底部间距? Heydon Pickering 的 “被切除脑叶的猫头鹰”选择器 是一种不错的方法,在我们等待 margin-trim
属性获得支持的同时可以使用。
啊,谦逊的模块!如今,许多设计都在内容型和应用型网站中使用模块。 一块信息、一则广告、一组功能……可以是任何东西。 它们可能具有视觉相似性,但可以包含任何内容,这带来了一个有趣的 CSS 挑战:如何一致地填充内部?
这是一个简单的示例,其中模块通过颜色差异与背景区分开来
html {
background: #333;
}
.module {
width: 20rem;
padding: 1.5rem;
margin: 1.5rem auto;
background: white;
}

但是,一旦该模块包含其他元素,填充就会“偏离”。
模块
Pellentesque habitant morbi tristique senectus…
段落很可能应用了一些全局排版规则,这些规则很可能具有一些底部边距。 即使您使用 选择性排版,您也可能在这里选择使用它,因为这里有文本。

看到较大的底部填充了吗?也许您觉得没问题,但我怀疑并非如此。 这种沉重感来自您在模块本身设置的一致填充加上段落的底部边距。

那么我们该怎么做呢?一个权宜之计是删除这些段落的边距。
.module p {
margin: 0;
}
但我相信我们都同意这很糟糕。 如果一个模块包含两个段落会怎样?现在它们会笨拙地彼此紧贴。
也许只有最后一个?
.module p:last-child {
margin: 0;
}
这可能行得通。但是这个修复仍然感觉有点笨拙。如果问题元素将来根本不是段落会怎样?它可能是一个
-
- 或者
-
-
- 。或者
-
- 。或者任何其他 HTML 元素……我们要列出每个 HTML 元素吗?
.module p:last-child,
.module ul:last-child,
.module ol:last-child,
.module dl:last-child {
/* losing battle */
margin: 0;
}
这绝对是一场必败的战斗。
但我们可以匹配任何元素……
.module *:last-child {
margin: 0;
}
您甚至可以省略那里的“*”,因为它被隐含且没有特异性,但我认为它使它更清晰。 我认为这更接近一个好的修复,但它有点笨重。 列表始终具有此操作会影响的子元素。 任何具有后代的元素都会成为问题。 为了解决这个问题,我们只需确保模块下只有顶级元素使用 子元素选择器 接受这种处理。
.module > *:last-child {
margin: 0;
}
这不错。我仍然不会称其为完美,因为后代实际上可能是最终额外底部填充的原因。 比如如果列表项中包含段落。 您可能需要将它们链接几次以匹配合理的 DOM 深度。
.module > *:last-child,
.module > *:last-child > *:last-child,
.module > *:last-child > *:last-child > *:last-child {
margin: 0;
}
这可能就可以了。
这是一个可视化演示
查看 Chris Coyier 在 CodePen 上的 Pen JFynD (@chriscoyier)
一种完全不同的方法可能是从模块中去除底部填充,并让子元素提供它,但我觉得这有点危险,因为无法保证子元素会自带自己的(例如一些通用的
).
您遇到过这种情况吗?您是否有自己喜欢的解决方案?
我所做的事情完全符合倒数第二个代码块。 实际上并没有考虑将 DOM 链接几次以确保安全。 无论哪种方式,看到我们对整个尾随边距问题有相同的解决方案感觉都很好。
我总是听说 * 选择器的性能很差。 那么,链接它是否会对性能构成威胁?
有人知道它对真实网页的影响有多大吗?
@Luca R.: 我一直使用这种方法,从未遇到过任何性能问题。 但我想这取决于项目的复杂性。
Luca - 我认为当您将其应用于网站上的每个元素时,* 选择器的性能更多地是讨论的话题,例如 * {}。 即使那样,我敢肯定,这里也已经讨论过它对性能的影响并不算太大。
好吧,这是个好消息。 :)
我肯定遇到过这种情况,但使用了类似您修复的第一个版本的代码,后来才发现我输了这场战斗。 这就是我放弃的地方。
非常感谢您提供一些启发!
嘿,Chris,以下内容怎么样?
http://codepen.io/catalinred/pen/yHsop
.module *:last-of-type {
margin: 0;
}
看起来您丢失了 h3 的边距,它很可能是模块中其类型的最后一个,但不太可能是最后一个子元素。
是的,我在回复后立即看到了。 我差一点就成功了! :)
我会这样做
.module *:last-child {
margin-bottom: 0;
}
添加了 示例
是的! 我只是使用此代码来解决底部间距问题,并遇到了删除所有边距后出现的其他间距问题。
margin-bottom: 0
是可行的!文章没有这样写,因为“它有点笨重”。 有没有办法在不链接子元素选择器的情况下进行优化?
这正是我过去所做的事情。 我没有想到后代,但问题从未出现过。 这是一个不错的解决方案 :)
我通常以两种方式之一解决此问题
1. 使模块在顶部和底部的填充较少,这由内容的边距来补偿。
2. 在容器底部设置 1px 填充(如果内容的边距与容器的填充相同)。
编号 2 有点“笨拙”,只有在您完全控制容器中的内容(并且您确定布局不会更改)时才应使用。
:last-child 与 IE8 不兼容(尽管您可能对完全支持 IE8 不感兴趣)。
或者,您可以设置所有内容仅具有顶部边距,这样您就可以定位 :first-child,或者使用 OOCSS 类结构(但这需要更多标记)。
仅顶部边距也不是完全好的,通常您希望同时具有顶部和底部边距,或者至少具有底部边距,但我认为这是一个选项。
类似这样:http://jsfiddle.net/ReinierK/CwReR/
编号 1 的唯一问题是,如果您首先有一个没有边距的元素(例如 div)。
如果您的内容仅包装在一个
<
div> 中,那么无论如何您在可访问性方面都会遇到其他问题。
我一直使用“湿绷带”的方法,
.module p:last-child { margin: 0; }
,然后在之后添加嵌套元素时注意并进行管理。这种方法相当混乱且效率低下。我也对更好的方法感兴趣!我通常的做法是在相邻兄弟元素的顶部添加外边距,而不是在每个元素的底部添加。我使用类似以下的代码:
p + p { margin-top: 1em; }
我会根据内容模块调整此代码。大多数情况下,它都能正常工作。
这是一个绝妙的主意,我得记住它。
我也使用过这种方法。对段落来说效果很好,但要使其适用于所有可能的相邻元素组合(使其万无一失)则非常困难(且代码冗长)。
我正要建议同样的方法。
我们在工作中使用此技巧。 :)
到目前为止,这是最好的方法。:-)
抢先一步!
我认为只清除`margin-bottom`很重要……模块内的某些重要的布局元素可能会因清除其顶部、左侧和右侧外边距而受到干扰,而我们真正想要做的只是底部间距。但这是一个极好的技巧!
嘿,Chris,
我只是好奇你使用通用选择器的原因——它在这个示例中起作用了吗?它对性能有帮助还是有害?如果删除它会怎样:http://codepen.io/mikevoermans/pen/ILida
只是好奇你的想法。
另一种解决方案是使用额外的类
// 移除第一个/最后一个元素的外边距
.first { margin-top: 0 !important; margin-left: 0 !important; }
.last { margin-bottom: 0 !important; margin-right: 0 !important; }
当我知道添加`:last-child`是过度杀伤时,我会使用它们,例如,因为某个情况很特殊,并且该规则只会匹配整个站点上的一个元素。
是的,这正是我想到的。依靠类比高级选择器更好,即使它更笨拙,不过,在这种情况下,我认为这更优雅一些。
好吧,我说“它更好”。我的意思是,我**感觉**依靠类比高级选择器更好。我认为高级选择器在您可以控制内容的环境中效果更好,例如个人网站。
并非如此。如果模块内容来自CMS,您将如何添加`first`和`last`类?
您需要通过后端或JavaScript插入它。
如果内容来自CMS,高级选择器将正常工作。
我讨厌遇到重新设计项目时,需要保留之前的HTML,但它充满了额外的类,例如每个地方都有`clearfix`。
HTML必须足够简单,以便随时替换CSS。使用过多的额外类不是一个好主意。
至少,在这种情况下,如果我替换了CSS,`first`和`last`类将不起作用。
为什么不在`.module`的底部使用负外边距?
你试过这个吗?
问题不在于模块的外边距,而在于模块的内边距。
我认为这应该可以使用,但需要使用`:after`伪元素(这可能在您的实际标记中已经考虑过,也可能没有)。
这是一个包含解决方案的修改后的Codepen链接。尚未在所有浏览器中进行测试。顶部外边距需要大于包含元素的任何底部外边距,并将强制最后一个元素与列折叠,以便在每个模块的底部有相同数量的额外空间。底部的负外边距只是通过将其吞噬来补偿该折叠的外边距。我通常会使用负外边距来处理此类技巧。
我非常喜欢这个解决方案。据我所知,在没有测试的情况下(我在用手机),它可以容纳任何最后一个元素(甚至是孙子元素)的底部外边距,然后用它自己的负外边距再次吞噬该外边距,对吧?太棒了;我期待尝试一下。框格式上下文一直是我的心头刺,我总是觉得依靠样式化最后一个直接后代——或者一系列最后一个后代的最后一个后代的最后一个后代——很不舒服。它总是显得过于脆弱,并且依赖于可预测的标记。这个方法“Just Works”——除非其中一个后代创建了自己的块级格式上下文并阻止了外边距折叠。
我真的很喜欢这个解决方案,仅仅因为它使用了`:after`伪选择器。我想这适用于IE 8及更高版本。
为什么不在每个第二个元素上添加顶部内边距?类似这样
.module > * ~ * {padding-top:1em} // 第一个元素之后的每个子元素
这样,您就可以分隔内容,并且不需要底部内边距?
我使用` .module > :last-child { margin-bottom: 0; }` + 一个通用的辅助类` .vertical-rhythm--none { margin-bottom: 0; }`来处理嵌套或奇特的案例。我只在一个方向(向下)应用垂直外边距。
我认为这是到目前为止最好的解决方案。
我非常喜欢单向外边距,它使一切变得惊人地可预测!我第一次从CSS Wizardry那里听到这个方法。
我在**Cascade Framework**中一直使用以下代码来处理整个站点网格结构中的额外内边距
.cell>:last-child {
margin-bottom: 0;
}
我选择使用`margin-bottom : 0`而不是`margin: 0`,因为选择器过于通用,无法重置顶部、左侧和右侧外边距。
出于同样的原因,我仅向下遍历了一级。我可能希望元素的子元素或其子元素具有底部外边距。对于这种通用的用例,无法知道。如果您确实需要重置子元素的子元素等等的外边距,在我看来,最好只使用基于类的选择器来调整它们。
我倾向于在基础排版级别(而不是模块级别)对所有块级元素执行一次此操作,例如(Sass)
p
margin-bottom: 2em
&:last-child
margin-bottom: 0
实际上希望`p`、`ul`或`ol`作为`:last-child`具有底部外边距的情况很少,因此在模块级别覆盖这些边缘情况很容易且合理。
哎呀,格式出错了,但你们明白我的意思:)
真正的垂直居中通常“看起来”不对劲。这就是为什么在印刷品中,您经常会看到它稍微偏向顶部。在我看来,您的“底部内边距较重”比完全没有内边距看起来更好。
哇,比我的方法简洁多了……从现在开始我将使用这种方法。谢谢!
如果可以在CSS本身中使用逻辑来检查这一点就好了,就像媒体查询一样。
我通常会添加顶部外边距/内边距
.module p {
margin: 20px 0 0;
}
这是一个示例.
或者一些简单的数学计算
$mod-bottom-padding: 1.5rem;
$mod-element-bottom-margin: 1rem;
.module {
padding-bottom: $mod-bottom-padding - $mod-element-bottom-margin;
}
如果模块元素都具有相同的底部外边距,则此方法有效。
使用折叠外边距和伪元素怎么样?请参见
http://codepen.io/getdave/details/gfEiq
我们创建一个伪元素,应用顶部外边距,然后将其“折叠”到最后一个元素的底部外边距中。如果模块填充基于基线高度/外边距,效果最佳。
哇,这太聪明了!看起来非常可靠,因为它依赖于外边距折叠的逻辑,而不是用 CSS 选择器进行猜测。有没有什么情况它不起作用?
这太棒了!您需要 1px 的高度吗?即使没有高度,如果它是 display: block,外边距还会出现吗?
恕我直言,唯一可预测、可持续的解决方案是使标记更具描述性。通过专门化模块类名,您可以以直接、惯用(且高效)的方式满足您的设计需求。演示笔。
(还有其他方法可以丰富标记。您可以保留模块的通用性,但将内容包装在一个具有专门类名的元素中。例如,本质上是
<module><module-titled-list><h3/><ul/></module-titled-list></module>
或<module><module-list><ul/></module-list></module>
。)模块应该是没有思想的通用僵尸的论点是一个有趣但人为的假设。无论渲染此模块的 CMS 或应用程序都可以增强,以便在内容输入时指定类型。如果您无法修改应用程序或渲染结构的任何内容,那么您将面临更大的设计问题。
感谢您发布这篇文章。
必须承认,我通常会走失败的路线。但是,在阅读本文后,我决定我的 CSS 需要更通用一些。感谢您发布这篇文章。
我建议只使用顶部外边距进行常规间距,因为在计划特定页面时,您大多数时候都知道页面开头的内容。您通常不知道页面将如何结束 - 是否会有分页组件、操作按钮、作者信息或其他内容(取决于特定模板的动态)。这样,您可以将整个页面排列成火车模型 - 机车是第一个没有减震器的,每个后续车厢都有自己的专门为此特定项目设计的间隔器。页面的第一个项目 99% 的时间都是相同的,标题或面包屑,因此您基本上只需要处理它即可。如果您必须只关注一侧的间隔器,那么遵循通用设计网格/间距会更容易。
多年来我一直使用顶部外边距进行间距,并且从未遇到过任何问题。因此,为了抛出一个特殊项 - IE7 支持 first-child:(因此您可以使用它)并且不支持 last-child
我将所有顶部外边距设置为 0,并且只使用底部外边距,如垂直节奏。我将模块的底部内边距设置为 0,因为任何包含的元素都将具有底部外边距。
使用此技术多年,从未遇到过大量填充的问题。
我一直只是将模块的底部填充关闭。
大多数内容项目需要底部填充/外边距,如果您考虑到这一点使用布局 div,只需也为它们提供一些底部填充即可。此外,您几乎不会以 H3 等结束模块。
我使用 sass 处理这个问题,使其保持抽象,以便可以将其重复用于每个需要它的模块
%last-child { & > :last-child { margin-bottom: 0; } }
因此,当模块需要移除其最后一个子元素的 margin-bottom 时
.module { @extend %last-child; }
如果
.module
始终包含一个具有margin-bottom
的元素,并且其外边距等于或大于内部padding
,则无需重置所有“最后一个”元素的margin-bottom
,而是将.module
的padding-bottom
设置为1px
。抛开语义不谈,我通常会使用一些辅助工具
.remove-margin-bottom {
margin-bottom: 0 !important;
}
在其他情况下
.give-margin-bottom {
margin-bottom: $spacing-variable !important;
}
可能听起来很糟糕,但是有很多时候我发现自己只是给一个元素添加一个类来获取或删除底部外边距。随意讨厌。;)
顺便说一句,拥有某种递归选择器可能很有用。例如
:last-child-recursive {margin-bottom: 0; }
将自动将所有嵌套的最后一个子元素的底部外边距归零 - 无论实际嵌套级别如何。
在 www-style 邮件列表中有一条关于此想法的消息。
当设计师出现并希望标题带有背景颜色或图像从模块边缘开始时会发生什么?现在您必须使用负偏移量,我猜这对于一个元素来说是可以的……
将其保留在此处
它使用外边距折叠来解决问题,这一点很巧妙。如果外边距/填充在大多数情况下相等并且子元素未浮动/内联块,则效果很好。
我将选择一个额外的类,因为它清楚地说明了我要删除此元素的填充的意图。使用类似_spaces.sass并从任何核心模块样式中删除所有外边距和填充。然后,您将拥有一个具有可重用外边距/填充类的间距组件,例如 .l-pbn(无底部填充)或 .l-mls(左侧小外边距)等。
{ margin-top: 10px; }
将为每个子元素提供顶部的外边距,除了第一个元素,这也解决了问题。另一种解决方案可能是使用具有外边距的元素而不是填充,并利用折叠外边距。
Chris 不要使用
.module > *:last-child{
//
}
使用
.module > :last-child{
//
}
它更快且更简洁
这样一个愚蠢的小问题,我发现自己经常思考它。感谢您汇总 Chris!我通常构建的是支持 IE8 的内容,因此我倾向于在大多数情况下使用 margin-top,并在需要时使用 first-child 将其归零。
我肯定遇到过这种情况。很棒的解决方案!
.module *:nth-last-child(1) { margin-bottom: 0; }
哎呀,这与
:last-child
的作用相同。没关系。nth-child 不适合 IE8
最好使用 !important 规则。
参见http://jsfiddle.net/guimihanui/9hCdC/
不。
这种类型的代码可能看起来非常基础,但它是一段非常棒的代码,并且有许多应用场景。感谢您发布此内容。
为什么不保留在 .module :last-child?
指定的规则很可能会覆盖此规则.. 例如:.module ul li { padding: 10px}
此外,我倾向于使用 Sugareina 提到的方法,认为相邻同级选择器比 :last-child 向后兼容性更好。
MHO
有时我会这样使用 :not 选择器
.my-spacing-element:not(:last-child) { margin-bottom: 15px; }
您也可以执行以下操作
.container { padding: 5px 20px 20px; }
.container > h1 { margin-top: 15px; }
.container > p { margin-top: 10px; }
.container > [任何其他您可能需要的元素] {margin-top: [某个值]px; }
假设容器始终以 h1 开头,这使我们在容器内部的每一侧都具有 20px 的良好间距,同时在每个 p 之前都有 10px 的空间(每个 h1 之前有 15px 的空间,除了第一个,因为容器间距一致),并且您无需以任何方式设置容器中第一个或最后一个元素的样式,与其他元素不同。
我发现它适用于大多数情况。当然,我通常不会开发那种需要一个容器适应客户端一开始塞进容器里的所有类型的元素的网站。:D
.module > * {margin:1em;}
为所有子元素提供了一个间隔良好的网格。如果需要,可以从各个元素中移除边距。过去五年左右我一直使用它,唯一棘手的地方是当.module没有任何边框时,边距可能会开始折叠。可以使用clearfix或overflow:hidden来解决这个问题,但这仍然有点hacky。
使用一些伪元素,您可以创建一个可靠的解决方案,而无需修改任何子元素。查看。
上面: 自包含的性感
再次提供该CodePen链接:http://codepen.io/thejameskyle/pen/pthGd(请务必查看我在其中创建的mixin!)
我会省略容器的底部间距。对我来说,这是最直接的方法,因为我非常不喜欢使用包含*选择器的任何东西。此外,:last-child在IE8中不受支持——现在这越来越不重要了,但这是一个你不能忘记的缺点。
嗨,Chris,不错的技巧!
我稍微修改了一下,包含了
first-child
,如下所示.module > *:first-child,
.module > *:first-child > *:first-child,
.module > *:first-child > *:first-child > *:first-child {
margin-top: 0;
}
.module > *:last-child,
.module > *:last-child > *:last-child,
.module > *:last-child > *:last-child > *:last-child {
margin-bottom: 0;
}
我看到的唯一问题是它不完全兼容IE8。
我有一个不同的解决方案,不需要移除多余的胶水。
多余的胶水??
让我解释一下:当您使用底部边距时,最后一个元素始终保留一个边距,该边距不会将其与任何内容分隔开。在提供的解决方案中,此边距始终会被添加,然后必须再次移除,就像多余的胶水一样。
我用于“模块”的代码改为使用顶部边距,但仅用于跟随其他元素的元素。
.module * + * { margin-top: 1.5em }
这表示“任何前面有其他元素的元素都应使用顶部边距分隔”。胶水仅在需要时才应用。
这适用于无限数量的嵌套级别,因为尽管每个嵌套元素都接收顶部边距,但没有第一个子元素这样做:无需担心或预测重复的边距。
由于
:first-child
具有更深层次的IE遗留支持,因此很久以前我就养成了仅将边距和填充应用于元素顶部并将其从第一个子元素中移除的习惯。http://stackoverflow.com/questions/7938521/browser-support-for-css-first-child-and-last-child这是添加到组合中的另一个半优化到次优的解决方案。它不添加任何新元素
好处是它解决了任何级别的嵌套,即使后代阻止其自身后代的边距折叠(浮动子元素、具有
overflow: hidden
的子元素、具有填充或边框的子元素等)。缺点是它期望所有子元素具有相同大小的边距(在本例中为1em)。