为什么margin-top设置百分比基于父元素的宽度计算的?

933 阅读6分钟

margin-top和padding-top设置百分比问题

我们都知道css很简单。所以在平时的开发当中,也没有在意一些具体的细节。我们知道某一个属性值大概的含义。比如我们知道css的盒模型,是由margin, border, padding, content 四个部分组成。如果我们想要设置一个元素的外边距,只需要设置margin值就可以。

margin取值可以为数值、百分比以及auto。margin为简写的形式,我们还可以分别设置margin的top、right、bottom、left值。比如我们写如下的代码:

    <style>
        .margin-container {
            background: #333;
            width: 400px;
            height: 200px;
            color: #fff;
        }
        .margin-child {
            margin-top: 10%;
            width: 200px;
            height: 100px;
            background-color: black;
        }
    </style>

    <div class="margin-container">
        <div class="margin-child">
            margin content
        </div>
    </div>

那么问题来了,其中margin-top: 10%;这个百分比,究竟是基于谁来计算的百分比呢?

再来看一个例子:

       <style>
        .padding-container {
            background: #333;
            width: 400px;
            height: 200px;
            color: #fff;
        }
        .padding-child {
            padding-top: 20%;
            width: 200px;
            height: 100px;
            background-color: black;
        }
    </style>
    

    <div class="padding-container">
        <div class="padding-child">
            padding content
        </div>
    </div>

此处的padding-top: 20%;又是基于谁来计算的百分比呢?

如果你已经清楚答案了, 那可能没有再继续阅读下去的必要。但是如果你还有疑问的话,那我们接下来就一探究竟。

结果分析

我们先不看结果,按照常规的思维来想一想。页面当中有一个.margin-container容器元素,一个.margin-child子元素。容器元素以及子元素都设置了相应的宽高。我们都知道块级元素在页面中是按照垂直方向挨个排列的,而margin-top是当前设置元素的外上边距,所以应该是在垂直方向上设置的一个距离。设置为百分比的话应该是根据元素的高度来进行相应的计算的。我们暂且先不管对错。

此处有两个问题:

  • margin-top设置百分比时是基于自身元素还是容器元素的值来计算的?
  • margin-top设置百分比时是基于height,还是width计算的?

接下来然我们看看具体的结果:

其实我们从这个图当中就能获取到我们想要的答案。

首先我们看margin-child的margin-top值设置为10%。在chrome开发者工具中,我们能看到其margin-top计算后的值为40px。而我们的margin-child宽高分别为200px、100px,margin-container宽高分别为400px、200px。所以我们可以得出结论margin-child所对应的margin-top为10%,是其父元素margin-container宽度400px的百分之十。所以以上两个问题对应的答案为:

  • margin-top设置百分比时是基于容器元素的值来计算的
  • margin-top设置百分比时是基于width计算的

当然,如果你再看看padding-top的设置。也会得到相同的结果。为什么?为什么会是基于宽度而非高度来计算?为什么是基于父元素而非子元素来进行计算?(此处另外如果大家注意观察的话,我们设置的是margin-child的margin-top为10%,我直觉上应该是会在margin-child与margin-container之间会有10%的距离,但是从渲染结果看,其实是margin-container与body的距离为10%,此处涉及margin合并的问题。如果不清楚可以自行查阅相关问题。)

我们从W3C规范文档中可以知道:

规范当中就是这么规定的,也就是浏览器或者说浏览器的渲染引擎(注:也许这么叫不是很恰当)就是这么来实现的。那为什么要这么规定?基于高度计算会有什么问题?

这篇文章给出了相应的一些说明,总结出来两点:

  • margin/padding计算都基于一个值,width进行计算,这样能够保证top,right,bottom,left四个值的一致性
  • 由于父元素的高度是根据其所包含的子元素进行计算的,如果子元素的margin/padding是基于父元素高度计算的话,那么会引起高度计算的循环依赖。

我们着重说一说第二点。

假设我们在一开始的示例中,没有给margin-container设置宽高

.margin-container {
    background: #333;
    color: #fff;
}
<div class="margin-container">
    <div class="margin-child">
        margin content
    </div>
</div>

此时父元素的宽为714px,高度为100px。因为目前只有一个子元素,子元素高度为100px,子元素的margin-top为父元素宽度的10%,计算后为71.4px。由于会存在误差所以渲染出来显示71.391px,并且子元素的margin与父元素的margin进行了合并,所以看起来是父元素距离body顶部71.391px。所以父元素的高度为100px。

试想如果我们按照高度来计算margin百分比的话,此时margin-child的margin-top值应该为100px*10%=10px。看起来似乎没有问题,但是,我们渲染的时候知道body宽度,由于没有设置margin-container的宽高,在渲染margin-contaner的时候需要计算它的宽高,此时我们可以确定其宽度为body宽度714px。高度未知,高度需要根据子元素的高度来进行计算,所以紧接着去进行margin-child的解析计算,发现child的高度为100px,margin-top为父元素高度的10%,而此时父元素高度又不确定,所以margin-child的margin-top应该是多少呢?

那有的人可能会说,父元素高度不是根据子元素高度计算么?子元素的高度为100px,所以父元素高度为100px,所以子元素的margin-top值为10px。如果浏览器引擎在解析css的时候是按照 解析子元素高度 -> 计算父元素高度 -> 计算子元素margin 这样的顺序的话,好像也是可以说的通的。

那我们再看看下面的例子:

.margin-container {
    background: #333;
    color: #fff;
}
.margin-child {
    margin-top: 10%;
    width: 200px;
    height: 100px;
    background-color: black;
}
<div class="margin-container">
    <div class="margin-child">
        margin content
    </div>
    <div class="margin-child">
        margin content 2
    </div>
</div>

运行结果为:

此时的父元素包含了两个子元素。父元素的高度计算为子第一个元素的高度+第二个子元素高度+第二个子元素的margin-top

那按照刚才的说法,如果浏览器引擎在解析css的时候是按照 解析子元素高度 -> 计算父元素高度 -> 计算子元素margin 这样的顺序的话,子元素高度和200px -> 父元素高度200px -> 子元素margin-top为10%=20px。再结合刚才运行的结果,父元素的高度计算为子第一个元素的高度+第二个子元素高度+第二个子元素的margin-top,此时父元素高度又发生了改变,那子元素的margin值还需要改变吗? 如果还需要改变的话,父元素的高度又会发生变化,此时就行程的循环依赖。

所以,你真的弄懂css中的margin和padding了吗?

以上为个人的浅见,其实有一些不严谨的地方,浏览器针对css解析以及渲染的顺序流程等,这儿权当是抛出了问题,不对的地方还望指出改正,一起探讨学习。