本文整理于:(布局和包含块 - CSS:层叠样式表 | MDN (mozilla.org))。 同时加上了自己的理解,并重新组织了介绍的顺序。
一个盒子中,需要进行百分比计算时,基准并不是上一级包裹的父盒子,而是其所在包含块的那个盒子。
也就是说,元素的width、height、top、bottom、left、right、margin、padding取值为百分比时,它们的计算基数都取决于:
-
它的父级链中,谁可以作为它的包含块(containing block,一个块级、行内块元素、格式上下文)。
-
子元素的定位情况,会决定其在包含块上获取计算基数时,是否要把padding加进来。
如果子元素定位为
static
(没有定位也是定位的一种)、relative
或sticky
,计算基数有两个:分别是包含块的width、height。如果子元素定位为absolute、fixed,计算基数有两个:包含块的width+左右padding、height+上下padding。
关于为什么定位为absolute、fixed时,要算上padding的个人记忆:
定位为absolute、fixed的子元素设置top、left属性设置为0时,会覆盖定位父盒子的padding,但不覆盖父盒子border,也就是把padding也当成父盒子"活动"区域了。而其它定位不会覆盖父盒子padding,计算基数自然只有width、height区域。
另外,父盒子box-sizing为border-box时,padding和内部真实width、height会一起分配css中设定的width、height值。在css中计算时,直接算css中的width或heitht即可,因为它包含padding了。
如何找到包含块
首先,一个可以成为包含块的元素,必须是非行内元素,可以设置宽高。如一个块级、行内块元素,或是一个格式化上下文。
然后,子元素的position定位属性,会决定其选取哪个元素作为包含块,有三种普通情况,一种特殊情况。
普通情况:
-
当子元素的定位为static(等于没设置)、relative、sticky,它的包含块为最近的非行内父盒子。
计算基数为包含块的:width、height(不含父盒子padding)。
-
当子元素定位为absolute,它的包含块为上级链中,定位不为static的盒子。
计算基数为包含块的:width+左右padding、height+上下padding。
-
当元素定位为fixed,它的包含块为当前视口(
100vw
、100wh
的区域)。计算基数为视口区域:
100vw
、100wh
。-
和文档的
scrollWidth
、scrollHeight
没关系。 -
需要注意的是,在显示设备中(屏幕),称为连续媒体,包含块是浏览器的视(窗)口区域。在pdf、打印机中,称为分页媒体,包含块是一页纸的区域。
-
特殊情况:
子盒子定位为absolut、fixed,其定位父盒子却是static定位元素的情况。
一般情况下,当子元素定位为absolute,进行定位时,会找父级链中定位属性不为static的盒子定位。当子元素定位为fixed,定位以视口区域进行定位。
-
但是当子元素的定位定位为absolute、fixed的情况下,还没找到满足上述条件的父盒子前,存在有以下特殊属性的更近的父盒子,记为A,则元素的包含块会变成A,并且定位也是相对于A定位。
absolut、fixed的定位的子元素,其定位父盒子可以是包含以下特殊属性的盒子,而不用关心父盒子的定位情况。
transform
或perspective
的值不是none
。(例如:perspective: 1px;
)will-change
的值是transform
或perspective
filter
的值不是none
或will-change
的值是filter
(只在 Firefox 下生效)。contain
的值是paint
(例如:contain: paint;
)。backdrop-filter
的值不是none
(例如:backdrop-filter: blur(10px);
)。
这种情况下,计算基数依旧是包含块(上面提到的A元素)的:width+左右padding、height+上下padding。说白了就是可选取的定位父盒子(包含块)范围变宽了,可以把上面普通情况的第二种情况,整理为:
- 当子元素定位为absolute,它的包含块为上级链中,找到第一个包含上面特殊属性的父盒子、或定位不为static的盒子作为包含块。
- 当子元素为fixed,它的包含块为上级链中,找到第一个包含上面特殊属性的父盒子,否则以视口作为包含块。
选取包含块的哪个基数计算
通过以上规则找到包含块后,根据子元素的定位情况就可以找出两个基数了:
- 子元素定位为普通定位:
static
(或没有定位)、relative
或sticky
- 横向的基数:包含块的width
- 纵向的基数:包含块的height
- 子元素定位为:position、fixed(包含块为含有特殊属性盒子)
- 横向的基数:包含块的width + 左右padding
- 纵向的基数:包含块的height + 上下padding
- 子元素定位为:fixed(包含块为视口区域)
- 横向的基数:
100vw
- 纵向的基数:
100vh
- 横向的基数:
以上已经找出了两个方向的基数,但选取哪个基数进行计算呢:
-
选取横向基数的属性:width、left、right、padding、margin
-
选取纵向基数的属性:height、top、bottom
例子
本文部分例子来源mdn,并进行了分类。可以去mdn示例查看部分例子的运行截图。
子元素定位为普通定位:static
(或没有定位)、relative
或 sticky
如果子元素的父盒子存在块级元素、行内块元素、格式上下文,则选取最近的符合该条件的父盒子作为包含块
css
/*<body> <section> <p><p/> </section> </body>*/
body {
background: beige;
}
section {
display: block;
width: 400px;
height: 160px;
background: lightgray;
}
p {
/* 这三种定位加了并不会影响section就是它包含块的事实
position: reative;
position: static;
position: sticky;
*/
width: 50%; /* == 400px * .5 = 200px */
height: 25%; /* == 160px * .25 = 40px */
margin: 5%; /* == 400px * .05 = 20px */
padding: 5%; /* == 400px * .05 = 20px */
background: cyan;
}
如果父元素中,只有行内元素,则直接选body为包含块
css
/*<body> <section> <p><p/> </section> </body>*/
body {
background: beige;
}
section {
display: inline;
background: lightgray;
}
p {
width: 50%; /* body元素的宽度一半 */
height: 100%; /* body的height为0,这里也为零 */
background: cyan;
}
子元素定位为:position、fixed
子元素定位为position,选取最近定位不为static的父元素为包含块。
- 前提:该父元素和子元素之间没有设置特殊属性的元素
css
/*<body> <section> <p><p/> </section> </body>*/
body {
background: beige;
}
section {
position: absolute;
left: 30px;
top: 30px;
width: 400px;
height: 160px;
padding: 30px 20px;
background: lightgray;
}
p {
position: absolute;
width: 50%; /* == (400px + 20px + 20px) * .5 = 220px */
height: 25%; /* == (160px + 30px + 30px) * .25 = 55px */
margin: 5%; /* == (400px + 20px + 20px) * .05 = 22px */
padding: 5%; /* == (400px + 20px + 20px) * .05 = 22px */
background: cyan;
}
子元素定位为fixed,包含块为视口区域
- 前提:子元素父级元素中,都没有含有特殊属性的元素
css
/*<body> <section> <p><p/> </section> </body>*/
body {
background: beige;
}
section {
width: 400px;
height: 480px;
margin: 30px;
padding: 15px;
background: lightgray;
}
p {
position: fixed;
width: 50%; /* == (50vw - (width of vertical scrollbar)) */
height: 50%; /* == (50vh - (height of horizontal scrollbar)) */
margin: 5%; /* == (5vw - (width of vertical scrollbar)) */
padding: 5%; /* == (5vw - (width of vertical scrollbar)) */
background: cyan;
}
absolute、fied子元素,选取含有特殊属性的父元素,如transform: rotate(0deg);
,作为包含块
- 前提:如果是absolute定位的子元素,该父元素和子元素之间只有定位为static的元素
css
/*<body> <section> <p><p/> </section> </body>*/
body {
background: beige;
}
section {
transform: rotate(0deg);
width: 400px;
height: 160px;
background: lightgray;
}
p {
position: absolute; /*position: fixed;同样效果*/
left: 80px;
top: 30px;
width: 50%; /* == 200px */
height: 25%; /* == 40px */
margin: 5%; /* == 20px */
padding: 5%; /* == 20px */
background: cyan;
}
案例反刍:宽高等比例缩放的盒子
这是一个非常经典的面试题。实现一个高度和宽度等比例缩放(示例中为1:1)的盒子。
如果能看懂这个案例,基本就理解了包含块了,欢迎留言讨论哦。
html
<template>
<div class="greaclar-father">
<div class="father">
<div class="children"></div>
</div>
</div>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
</script>
<style scoped>
.greaclar-father {
box-sizing: border-box; /* 下面定义的width:600px包含左右各50px的padding和500px的实际width */
width: 600px;
height: 400px;
padding: 50px;
perspective: 0px; /*同样可以成为 .father 元素的定位父盒子(包含块)*/
background-color: #e55b5b;
}
.father {
position: fixed;/* 会获取其包含块padding及内部的宽高来计算百分比 */
box-sizing: border-box;
width: 50%; /* 600px * 50% = 300px */
padding-top:50% ; /* 600px * 50% = 300px */
background-color: #bedc46;
}
.children {
position: absolute; /* 会获取其包含块padding及内部的宽高来计算百分比 */
top: 0;
left: 0;
width: 100%; /* 包含块width + 左右padding = 300px + 0 = 300px */
height: 100%; /* 包含块height + 上下padding = 0 + 300px = 300px */
background-color: #5bd440;
}
</style>