likes
comments
collection
share

你不知道的 CSS 之包含块

作者站长头像
站长
· 阅读数 18

但是一说到 CSS 包含块,有的小伙伴就懵圈了,什么是包含块?好像从来没有听说过这玩意儿。

你不知道的 CSS 之包含块

如果你不知道包含块,那么咱们开始了。

正文

平时你在书写 CSS 时,你是感受不到它的存在,所以不知道这个玩意儿也很正常。但是这玩意儿是存在的,在 CSS 规范中也是明确书写了的:

drafts.csswg.org/css2/#conta…

你不知道的 CSS 之包含块

如果你不了解它的运作机制,有时就会出现一些你认为的莫名其妙的现象。

那么,这个包含块究竟说了什么内容呢?

我们来看看MDN文档上是怎么说的

developer.mozilla.org/zh-CN/docs/…

你不知道的 CSS 之包含块

说起来也简单,就是元素的尺寸和位置,会受它的包含块所影响。对于一些属性,例如 width, height, padding, margin,绝对定位元素的偏移值(比如 position 被设置为 absolute 或 fixed),当我们对其赋予百分比值时,这些值的计算值,就是通过元素的包含块计算得来。

先来看看第一个例子

<body>
  <div class="container">
    <div class="item"></div>
  </div>
</body>
        .container {
            width: 500px;
            height: 300px;
            background-color: #46464f;
            position: relative;
            margin: 50px auto;
            padding: 20px;
        }

        .item {
            width: 50%;
            height: 50%;
            margin-left: 20px;
            background-color: rgb(208, 110, 110);
        }

请仔细阅读上面的代码,然后你认为 div.item 这个盒子的宽高是多少?

你不知道的 CSS 之包含块

相信你能够很自信的回答这个简单的问题,div.item 盒子的 width 为 250px,height 为 150px。

但是如果我追问你是怎么得到这个答案的,我猜不了解包含块的你大概率会说,因为它的父元素 div.container 的 width 为 500px,50% 就是 250px,height 为 300px,因此 50% 就是 150px。

但其实是不对的,正确答案应该是:div.item 的宽高是根据它的包含块来计算的,而这里包含块的大小,正是这个元素最近的祖先块元素的内容区。所以答案如下:

  • .container 的宽度和高度分别为 500px 和 300px。

  • .container 的 padding 为 20px,这意味着 .container 的实际内容区域(即去除 padding 的部分)的宽度为 500px - 40px = 460px,高度为 300px - 40px = 260px。

  • .item 的 width 和 height 均设置为 50%,这意味着它们分别等于 .container 内容区域宽度和高度的一半。 因此,.item 的实际宽度和高度计算如下:

  • 宽度: 460px / 2 = 230px

  • 高度: 260px / 2 = 130px 尽管 .item 的 margin-left 设置为 20px,但这不会影响 .item 的实际宽度和高度;它只会影响 .item 在 .container 中的水平位置。

包含块分为两种,一种是根元素(HTML 元素)所在的包含块,被称之为初始包含块(initial containing block)。对于浏览器而言,初始包含块的的大小等于视口 viewport 的大小,基点在画布的原点(视口左上角)。它是作为元素绝对定位和固定定位的参照物。

另外一种是对于非根元素,对于非根元素的包含块判定就有几种不同的情况了。大致可以分为如下几种:

  • 如果元素的 positiion 是 relative 或 static ,那么包含块由离它最近的块容器(block container)的内容区域(content area)的边缘建立。
  • 如果 position 属性是 fixed,那么包含块由视口建立。
  • 如果元素使用了 absolute 定位,则包含块由它的最近的 position 的值不是 static (也就是值为fixed、absolute、relative 或 sticky)的祖先元素的内边距区的边缘组成。

前面两条实际上都还比较好理解,第三条往往是初学者容易比较忽视的,我们来看一个示例:

<body>
    <div class="container">
      <div class=box1">
        <div class="box2"></div>
      </div>
    </div>
  </body>
        .container {
            width: 500px;
            height: 300px;
            background-color: rgb(91, 104, 109);
            position: relative;
        }

        .box1 {
            width: 300px;
            height: 150px;
            border: 5px solid;
            margin-left: 100px;
        }

        .box2 {
            width: 100px;
            height: 100px;
            background-color: rgb(20, 127, 30);
            position: absolute;
            right: 10px;
            top: 10px;
        }

首先阅读上面的代码,然后你能在脑海里面想出其大致的样子么?或者用笔和纸画一下也行。

公布正确答案:

你不知道的 CSS 之包含块 怎么样?有没有和你所想象的对上?

其实原因也非常简单,根据上面的第三条规则,对于 div.box2 来讲,它的包含块应该是 div.container,而非 div.box1。

如果你能把上面非根元素的包含块判定规则掌握,那么关于包含块的知识你就已经掌握 80% 了。

实际上对于非根元素来讲,包含块还有一种可能,那就是如果 position 属性是 absolute 或 fixed,包含块也可能是由满足以下条件的最近父级元素的内边距区的边缘组成的:

  • transform 或 perspective 的值不是 none
  • will-change 的值是 transform 或 perspective
  • filter 的值不是 none 或 will-change 的值是 filter(只在 Firefox 下生效).
  • contain 的值是 paint (例如: contain: paint;)

我们还是来看一个示例:

<body>
  <div class="container">
    <div class="box1">
      <div class="item2"></div>
    </div>
  </div>
</body>
    .container {
            width: 500px;
            height: 300px;
            background-color: rgb(91, 104, 109);
            position: relative;
        }

        .box1 {
            width: 300px;
            height: 150px;
            border: 5px solid;
            margin-left: 100px;
            transform: rotate(0deg);
        }

        .box2 {
            width: 100px;
            height: 100px;
            background-color: rgb(20, 127, 30);
            position: absolute;
            right: 10px;
            top: 10px;
        }

我们对于上面的代码只新增了一条声明,那就是 transform: rotate(0deg),此时的渲染效果却发生了改变,如下图所示:

你不知道的 CSS 之包含块

可以看到,此时对于 div.box2 来讲,包含块就变成了 div.box。

我们再来看一下MDN给包含块举得第五个例子。

<body>
  <section>
    <p>This is a paragraph!</p>
  </section>
</body>
body {
  background: beige;
}

section {
  transform: rotate(0deg);
  width: 400px;
  height: 160px;
  background: lightgray;
}

p {
  position: absolute;
  left: 80px;
  top: 30px;
  width: 50%; /* == 200px */
  height: 25%; /* == 40px */
  margin: 5%; /* == 20px */
  padding: 5%; /* == 20px */
  background: cyan;
}

上面是一段简单的 HTML 代码,在没有添加任何 CSS 代码的情况下,你能说出各自的包含块么?

根据CSS规范和代码,我们可以列出每个元素的包含块如下:

元素包含块
htmlinitial C.B.
bodyhtml
sectionbody
psection
  • HTML: 初始包含块(initial containing block),其尺寸依赖于用户代理(通常是视口大小)。
  • body: 包含块为 HTML 元素。
  • section: 作为静态定位的块级元素,其包含块是其父元素 body。
  • p: 使用了 position: absolute,其包含块是最近的非静态定位(non-static positioned)祖先元素的内边距区域,这里是 section。

注意,由于 p 是绝对定位的,其包含块是 section 而不是 body,即使 section 没有显式的位置属性。这是因为 section 默认具有 position: static,但它成为了包含块,因为它是 p 最近的非绝对定位祖先。

此外,sectiontransform: rotate(0deg) 使其成为包含块的候选者,即使旋转角度为0度。这是因为只要 transform 属性被设置(即使值为 nonerotate(0deg)),元素就可能成为其后代元素的包含块。

因此,正确的描述应当是:

  • HTML 作为根元素,其包含块是初始包含块,对于浏览器而言,初始包含块的大小等于视口的大小,基点位于视口的左上角。
  • body 是静态定位的元素,其包含块是 HTML 元素。
  • section 作为块级元素,其包含块是其父元素 body。
  • p 作为绝对定位的元素,其包含块是 section,即使 section 的 position 默认为 static,因为 section 的 transform 不为 none,使其成为 p 的包含块。

接下来修改一下代码:

<body> 
    <section> 
        <p>This is a paragraph!</p> 
        <div class="additional-element">
            This is an additional element to make the section grow.
        </div> 
    </section> 
</body>
p {
  position: relative;
  width: 100%;       
  height: auto;      
  margin-bottom: 20px;
  padding: 10px;      
  background: cyan;   
}

/* 新增的元素类(.additional-element)的样式 */
.additional-element {
  width: 100%;        
  padding: 10px;      
  background-color: peachpuff;
}

让我们根据你最后提供的代码和场景来详细描述每个元素的包含块。在给出的代码中,没有元素被绝对定位,所有元素都遵循正常的流布局。下面是各个元素的包含块概述:

元素包含块
html初始包含块(initial containing block),其大小和位置由浏览器(用户代理)决定,通常是视口的大小。
bodyhtml 元素,它作为 body 的直接容器。
sectionbody,因为它是 section 的直接父元素。
<p>section,尽管 <p> 被设置为 position: relative,但是这并不改变其包含块,它的包含块仍然是其直接父级 section
.additional-elementsection,因为 .additional-elementposition 属性保持默认值 static,所以它遵循正常的文档流,其包含块就是其最近的父级元素,这里是 section

由于 <p>position 被设置为 relative,这仅会影响 <p> 相对于其正常位置的偏移,但不会改变其包含块。同样,.additional-element 由于没有特殊的定位属性,其包含块自然就是 section

总的来说,没有绝对定位或固定定位的元素,它们的包含块就是它们的直接父元素,除非父元素通过 transformperspectivefilter 属性而成为包含块。在这种情况下,即使父元素的 position 属性是 static,它仍然会成为包含块。然而,在当前的代码中,没有元素触发这一规则,因此每个元素的包含块都是其直接父元素。

另外,关于包含块的知识,在 MDN 上除了解说了什么是包含块以外,也举出了很多简单易懂的示例。

具体你可以移步到:developer.mozilla.org/zh-CN/docs/…

总结

我们来总结一下大致内容:

  1. 包含块的基本定义:元素的尺寸和位置受其包含块的影响。当给元素的宽度、高度、内边距、外边距等属性赋以百分比值时,这些值基于元素的包含块来计算。

  2. 初始包含块:根元素(HTML元素)的包含块称为初始包含块,其大小等于浏览器视口的大小。

  3. 非根元素的包含块

    • 如果元素的positionrelativestatic,其包含块由最近的块容器的内容区域边缘建立。
    • 如果positionfixed,包含块由视口建立。
    • 如果元素使用absolute定位,包含块由最近的position不为static的祖先元素的内边距区边缘组成。
  4. 特殊情况下的包含块:如果positionabsolutefixed,且祖先元素的transformperspectivefilter属性不为none,或will-change属性为transformperspectivefilter,则包含块可能由满足条件的最近父级元素的内边距区边缘组成。

好了,我的见解到这就结束了,希望对你有所帮助

转载自:https://juejin.cn/post/7397617342162878474
评论
请登录