为啥 CSS 中的边距/填充百分比总是根据宽度计算?

Posted

技术标签:

【中文标题】为啥 CSS 中的边距/填充百分比总是根据宽度计算?【英文标题】:Why are margin/padding percentages in CSS always calculated against width?为什么 CSS 中的边距/填充百分比总是根据宽度计算? 【发布时间】:2012-06-15 18:19:14 【问题描述】:

如果您查看CSS box model spec,您会看到以下内容:

[margin] 百分比是相对于生成框的包含块的宽度 计算的。 请注意,'margin-top' 和 'margin-bottom' 也是如此。 如果包含块的宽度取决于此元素,则结果布局在 CSS 2.1 中未定义。 (强调我的)

确实如此。但是为什么?到底是什么迫使任何人以这种方式设计它?很容易想到您想要的场景,例如某件事总是从页面顶部向下 25%,但是很难想出任何理由让您希望垂直填充相对于父级的水平大小。

这是我所指的现象的一个例子:

<div style="border: 1px solid red; margin: 0; padding: 0; width: 200px; height: 800px;">
  This div is 200x800.
  <div style="border: 1px solid blue; margin: 10% 0 0 10%;">
    This div has top-margin of 10% and left-margin of 10% with respect to its parent.
  </div>
</div>

http://jsfiddle.net/8JDYD/

【问题讨论】:

我最初的想法是它会导致 margin: 25% 的实际含义模棱两可。即使代码表明是这样,它也不会是一个均匀的边距。我没有证据支持这一点,但这似乎是合理的。 jsFiddle of my thoughts。高度比宽度更易变化,因此每当内容发生变化时,它会使边距以难以预测的方式移动。 @Ryan:是的,这不会是一个均匀的边距,但话又说回来,如果你说height: 10%; width: 10%,你也不会得到一个正方形元素。 根据dev.w3.org/csswg/css3-box/#the-margin-properties 声明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). 所以它是双向的 @Ryan 我认为我们有赢家。在这里寻找演示 - jsFiddle 【参考方案1】:

将我的评论转移到答案,因为它合乎逻辑。但请注意,这是毫无根据的猜想。以这种方式编写规范的实际原因在技术上仍然未知。

元素高度由元素的高度定义 孩子们。如果一个元素有 padding-top: 10% (相对于父元素 高度),这将影响父母的高度。由于 孩子的身高取决于父母的身高,而 父母的身高取决于孩子的身高,我们将 要么高度不准确,要么无限循环。当然,这只是 影响偏移父===父的情况,但仍然。这是一个 难以解决的奇案。

更新:最后几句话可能并不完全准确。叶元素(没有子元素的子元素)的高度会影响其上方所有元素的高度,因此这会影响许多不同的情况。

【讨论】:

请注意,这条规则也有例外——CSS2.1 的第 10.6 节几乎涵盖了计算各种类型盒子高度的所有实例。事实上,涉及父母和孩子身高相互依赖的情况往往会导致未定义的行为。 @sanjaypoyzer 我猜在最简单的情况下,浏览器会从外部计算块宽度(根到提示),然后将内容流入这些块以确定它们的高度(提示到根)。 为什么 W3C 经常这样做?显然,对 W3C 来说,“语义”就相当于必须迎合那些无法进行逻辑思考的人,这是没有意义的。这就像抱怨世界上的人是多么的混乱和愚蠢,然后传播更多的混乱和愚蠢。您提供的推理没有意义:同样的论点适用于宽度,但效果很好。所需要的只是设置确定高度的上下文和方向,除非父对象的高度以像素为单位设置或不可变,否则问题不大。 这种依赖是一个代数公式,它有一个解决方案,并且不会导致无限循环,除非您选择以不尊重这一点的方式解决它。假设父母身高是h_p。 10% 的内边距将使子级高度为h_c = h_ci + 0.1h_p,其中h_ci 是子级的内部高度,不依赖于父级高度。对于一个孩子,您只需从等式h_p = h_ci + 0.1h_p => h_p = h_ci / 0.9 中找到父母身高。无论您有多少孩子,您都可以随时执行此操作,即使填充不同。它只有一个未知数 (h_p) 和一个方程式。 我不认为无限计算是原因。一个简单的原因是:使用width 会导致水平相同的问题。【参考方案2】:

要使“n%”边距(和填充)与 margin-top/margin-right/margin-bottom/margin-left 的 相同,所有四个必须相对于相同根据。如果 top/bottom 使用的 base 与 left/right' 不同,那么“n%”边距(和填充)在所有四个边上的含义都不相同。

(另请注意,相对于 width 的顶部/底部边距会启用一个奇怪的 CSS hack,它允许您指定一个具有不变纵横比的框……即使该框被重新缩放。 )

【讨论】:

...一个非常简单的答案。尽管上述所有猜想都有其优点,但这是一个很好的观点。看起来可能有多个解决方案是正确的,所以他们只选择了最有可能使用的一个......也许? 我想在语法中有一个额外的元素会很好,这样你就可以写padding: n [type]。因此,您将拥有 padding: 5% logical(OP 建议的内容),而不是 padding: 5% width,第二个参数默认为“宽度”以实现向后兼容性。 “n% 边距(和填充)不会在所有四个方面都表示相同的意思” - 但为什么会出现问题呢?【参考方案3】:

在玩了这个JSFiddle(在Chrome 46.0.2490.86上)并参考this post(用中文写)之后,我投票给@ChuckKollars的答案。


反对无限计算猜想的一个主要原因是:使用width面临同样的无限计算问题。

看看这个JSFiddle,parent 显示的是inline-block,可以在上面定义边距/填充。 child 的保证金值为 20%。如果我们遵循无限计算猜想:

    child 的宽度取决于parent parent 的宽度取决于child

但结果,Chrome 在某处停止了计算,结果是:

如果您尝试在 JSFiddle 上水平展开“结果”面板,您会发现它们的宽度不会改变。请注意child 中的内容被包装成两行(不是,比如说,一行),为什么?我猜 Chrome 只是在某个地方硬编码它。如果你编辑child的内容使其更多(JSFiddle),你会发现只要横向有多余的空间,Chrome就会将内容保留两行。

所以我们可以看到:有一些方法可以防止无限计算


我同意这样的猜想:这种设计只是为了保持四个边距/填充值基于相同的度量。

this post(中文写)也提出另一个原因是:是因为阅读/排版的方向。我们从上到下阅读,宽度固定,高度无限(实际上)。

【讨论】:

这是因为网页在通常无限的方向上垂直滚动;所以宽度可以从浏览器窗口的宽度计算出来。元素比屏幕宽的唯一方法是,如果您指定它们的宽度更大。典型的块元素宽度为 100%,并在垂直方向自动扩展(未知)。这就是为什么宽度没有同样的问题。【参考方案4】:

我意识到 OP 在问 为什么 CSS 规范将顶​​部/底部边距百分比定义为宽度的百分比(而不是假设的高度),但我认为它也可能是有用的发布一个潜在的解决方案。

现在大多数现代浏览器都支持 vw 和 vh ,这使您可以根据视口宽度和视口高度指定边距数字。

如果没有滚动条,100vw/100vh 分别等于 100% 宽度/100% 高度;如果有滚动条,则视口数字不考虑这一点(而 % 数字可以)。值得庆幸的是,几乎所有浏览器都使用 17 像素大小的滚动条 (see here),因此您可以使用 css calc 函数来解决这个问题。如果你不知道滚动条是否会出现,那么这个解决方案是行不通的。

例如:假设没有水平滚动条,50% 高度的上边距可以定义为“margin-top: 50vh;”。使用水平滚动条,这可以定义为“margin-top: calc(0.5 * (100vh - 17px));” (请记住,calc 中的减号和加号运算符两边都需要空格!)。

【讨论】:

在许多情况下这是一个有用的解决方法,但是有很多例子表明它不起作用(例如,如果你试图使内容与 flexbox 容器的大小成比例地增长到填充可用空间,同时还有另一个内容可变的框)。【参考方案5】:

我知道这个问题有点老了,但我想为 CSS3 刷新它。虽然 CSS2.1 规范确实说百分比填充和边距是相对于包含块的宽度定义的,但情况并非总是。这取决于写作模式。这直接来自CSS3 specs:

因此,margin 和 padding 属性的百分比(在 CSS2.1 中总是根据包含块的宽度计算),在 CSS3 中是根据包含块的内联大小计算的。

我在我的tutorial on aspect ratios with CSS 中介绍了这一点。

具体来说,有一个关于Percentage Padding in Horizontal vs. Vertical Writing Modes 的部分。默认情况下,元素具有水平书写模式,其中文本从左到右水平流动(在“内联”方向)。但是,使用writing-mode CSS 属性,您实际上可以将模式设置为垂直(文本从右到左或从左到右流动)。下面是一些横写模式和竖写模式的图表:

这些来自MDN docs on writing modes。

在垂直书写模式下,百分比填充将相对于包含块的高度,而不是宽度。

这是证据:

.document 
  writing-mode: vertical-rl;
  width: 100%;
  height: 100vh;


.parent 
   width: 100%;
   height: 200px;
   background-color: black;
   color: white;


.child 
  padding: 10%;
  background-color: white;
  color: black;
  border: solid 1px;
<div class="document">
  <div class="parent">
    <div class="child">
      Child
    </div>
  </div>
</div>

孩子获得 20px 的内边距,这是其包含块高度 (200px) 的 10%。

至于问题中的原因,这里的其他帖子对此进行了很好的介绍。

【讨论】:

以上是关于为啥 CSS 中的边距/填充百分比总是根据宽度计算?的主要内容,如果未能解决你的问题,请参考以下文章

让两个 CSS 元素并排填充它们的容器,并带有边距

CSS 使用百分比和边距、填充或边框

响应式 div 宽度结合固定宽度 div 纯 CSS

如何根据自己的宽度而不是父 div 将 CSS 边距/填充设置为子 div?

如何在 Photoshop 中测量/转换 CSS 文本边距/填充?

在css中设置主要内容相对于响应侧边栏宽度的边距[重复]