上级包裹盒子一定会影响子盒子css中的百分比计算吗,overflow:hidden一定会生效吗

本文整理于:(布局和包含块 - CSS:层叠样式表 | MDN (mozilla.org))。
同时加上了自己的理解,并重新组织了介绍的顺序,补充了相关的内容。并且把行高、字体大小的百分比也进行了归纳介绍。绝对值得一读。

一个盒子中,需要进行百分比计算时,基准并不是上一级包裹的父盒子,而是其所在包含块的那个盒子。

也就是说,元素的width、height、top、bottom、left、right、margin、padding取值为百分比时,它们的计算基数都取决于:

  • 它的父级链中,谁可以作为它的包含块(containing block,一个块级、行内块元素、格式上下文)。

  • 子元素的定位情况,会决定其在包含块上获取计算基数时,是否要把padding加进来。

    如果子元素定位为static(没有定位也是定位的一种)、relativesticky,计算基数有两个:分别是包含块的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定位属性,会决定其选取哪个元素作为包含块,有三种普通情况,一种特殊情况。

普通情况:

  1. 当子元素的定位为static(等于没设置)、relative、sticky,它的包含块为最近的非行内父盒子。

    计算基数为包含块的:width、height(不含父盒子padding)。

  2. 当子元素定位为absolute,它的包含块为上级链中,定位不为static的盒子。

    计算基数为包含块的:width+左右padding、height+上下padding。

  3. 当元素定位为fixed,它的包含块为当前视口(100vw100wh的区域)。

    计算基数为视口区域:100vw100wh

    • 和文档的scrollWidthscrollHeight没关系。

    • 需要注意的是,在显示设备中(屏幕),称为连续媒体,包含块是浏览器的视(窗)口区域。在pdf、打印机中,称为分页媒体,包含块是一页纸的区域。

特殊情况:

子盒子定位为absolut、fixed,其定位父盒子却是static定位元素的情况。

一般情况下,当子元素定位为absolute,进行定位时,会找父级链中定位属性不为static的盒子定位。当子元素定位为fixed,定位以视口区域进行定位。

  • 但是当子元素的定位定位为absolute、fixed的情况下,还没找到满足上述条件的父盒子前,存在有以下特殊属性的更近的父盒子,记为A,则元素的包含块会变成A,并且定位也是相对于A定位。

    absolut、fixed的定位的子元素,其定位父盒子可以是包含以下特殊属性的盒子,而不用关心父盒子的定位情况。

    1. transformperspective 的值不是 none。(例如:perspective: 1px;
    2. will-change 的值是 transformperspective
    3. filter 的值不是 nonewill-change 的值是 filter(只在 Firefox 下生效)。
    4. contain 的值是 paint(例如:contain: paint;)。
    5. backdrop-filter 的值不是 none(例如:backdrop-filter: blur(10px);)。

这种情况下,计算基数依旧是包含块(上面提到的A元素)的:width+左右padding、height+上下padding。说白了就是可选取的定位父盒子(包含块)范围变宽了,可以把上面普通情况的第二种情况,整理为:

  • 当子元素定位为absolute,它的包含块为上级链中,找到第一个包含上面特殊属性的父盒子、或定位不为static的盒子作为包含块。
  • 当子元素为fixed,它的包含块为上级链中,找到第一个包含上面特殊属性的父盒子,否则以视口作为包含块。

选取包含块的哪个基数计算

通过以上规则找到包含块后,根据子元素的定位情况就可以找出两个基数了:

  1. 子元素定位为普通定位:static(或没有定位)、relativesticky
    • 横向的基数:包含块的width
    • 纵向的基数:包含块的height
  2. 子元素定位为:position、fixed(包含块为含有特殊属性盒子)
    • 横向的基数:包含块的width + 左右padding
    • 纵向的基数:包含块的height + 上下padding
  3. 子元素定位为:fixed(包含块为视口区域)
    • 横向的基数:100vw
    • 纵向的基数:100vh

以上已经找出了两个方向的基数,但选取哪个基数进行计算呢:

  • 选取横向基数的属性:width、left、right、padding、margin

  • 选取纵向基数的属性:height、top、bottom

行高、字体百分比

这里引入font-size和line-height是为了呼应题目,font-size的百分比计算确实是受上级包裹盒子的影响。line-height百分比计算则是与自身元素的font-size有关,和包裹盒子并不是绝对关系。和上文、后文主要引出的包含块没有关联。

font-size、line-height是可继承的属性,任何类型的元素都可以继承与被继承,和包含块没关系。和上面的属性属于两个范畴了。

font-size的百分比计算:

  • 直接读取上一个包裹元素的font-size(上一个元素的font-size可能也是继承过来的,所以肯定有值),并乘以百分比。

    <div style="font-size: 20px;">
        <p style="font-size: 150%;">
        	<!-- 等价于:font-size: 1.5em; -->
            <!-- p: 20px * 150% = 30px -->
            欢迎关注点赞greaclar,一起发现更多技术细节与规律
        </p>
      </div>
    

line-height的百分比计算:

  • line-heitht的百分比计算会读取当前元素的font-size属性(可能是继承过来的),并乘以百分比。

    无论是em单位还是百分比,都是读取自身的font-size,在继承上级元素的font-size时,才是拿上一级元素的font-size,但本质还是拿自己的font-size。

    <div style="font-size: 30px; line-height: 150%;">
        <p style="line-height: 100%;">
        	<!-- 等价于:line-height: 1em; -->
        	<!-- p:line-height 30px * 100% = 30px -->
            欢迎关注点赞greaclar,一起发现更多技术细节与规律
        </p>
      </div>
    

line-height属性继承的百分比问题:

line-height定义在自身元素上时,按照上面的方式,找到自身的font-size计算即可(可能是继承过来的font-size值)。
如果line-height是继承过来的就比较危险了,有两种情况:

  • 继承的源头line-height,它的原始值是百分比或em单位。计算基值是源头line-height所在元素的font-size。
    本质是,该line-height在源头元素中就已经被计算出绝对的px长度了,被继承时,已经是绝对值了,不会在有百分比的概念。
  • 继承的源头line-height,它的原始值是不带单位的比值。计算基值是继承所在元素自身的font-size。
    本质是,继承过来的line-height是原始的比值,还没计算出绝对的px长度。被继承后,需要和宿主的font-size进行计算。所以这种line-height的继承效果,等价于把line-height直接定义在元素自身上。
<!-- 百分比 -->
<div style="font-size: 30px; line-height: 100%;">
    <div style="font-size: 40px;">
        <p style="font-size: 60px;">
            <!-- p:line-height 30px * 100% = 30px -->
            欢迎关注点赞greaclar,一起发现更多技术细节与规律
        </p>
    </div>
</div>

<!-- 数值比值 -->
<div style="font-size: 30px; line-height: 1;">
    <div style="font-size: 40px;">
        <p style="font-size: 60px;">
            <!-- p:line-height 60px * 1 = 60px -->
            欢迎关注点赞greaclar,一起发现更多技术细节与规律
        </p>
    </div>
</div>

例子

本文部分例子来源mdn,并进行了分类。可以去mdn示例查看部分例子的运行截图。

子元素定位为普通定位:static(或没有定位)、relativesticky

如果子元素的父盒子存在块级元素、行内块元素、格式上下文,则选取最近的符合该条件的父盒子作为包含块

/*<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为包含块

/*<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的父元素为包含块。

  • 前提:该父元素和子元素之间没有设置特殊属性的元素
/*<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,包含块为视口区域

  • 前提:子元素父级元素中,都没有含有特殊属性的元素
/*<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的元素
/*<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)的盒子。

如果能看懂这个案例,基本就理解了包含块了,欢迎留言讨论哦。

<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>

包裹盒子无法overflow:hidden截断子盒子

包含块除了可以作为内部子元素的定位参照、百分比计算基数,也是overflow:hidden唯一可生效于该子元素的父盒子。也就是说,如果子元素(记为X)和其包含块之间,还有中间父盒子,这些非包含块父盒子的overflow:hidden对于子元素X来说是无效的。

示例解析:

  • 以下示例中greaclar-father盒子设置了perspective: 0px;,成为positon:absolute定位的children盒子的包含块。即使father盒子是children的第一个包裹父盒子,也设置了overflow:hiddern,但children盒子还是会超出father盒子完整显示。

建议:

  • 给元素设置overflow时,需要留意内部是否有position属性值为absolute、fixed的子元素。并确保这些子元素的包含块是自己。
  • 但是,为了代码的可维护性,最好不要在设置了overflow的元素内部,对子元素进行absolute、fixed定位。
<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;
    height: 400px;
    padding: 50px;
    perspective: 0px; /*含有特殊属性,并作为.children的包含块*/
    background-color: #e55b5b;
}
.father {
    /*.father 不满足成为.chindrend的包含块条件,无法截断.children*/
    overflow: hidden; 
    box-sizing: border-box;
    width: 300px;
    padding-top:300px ;
    background-color: #bedc46;
}
.children {
    /* absolute定位会查找包含特殊属性或定位不为static的父盒子作为包含块 */
    position: absolute; 
    top: 0;
    left: 0;
    width: 700px; /*长度超出father部分不会被father盒子截断*/
    height: 100px;
    background-color: #5bd440;
}
</style>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值