上级包裹盒子一定会影响子盒子css中的百分比计算吗

本文整理于:(布局和包含块 - 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

例子

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

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

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

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>
相关推荐
FØund40410 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
酷酷的威朗普14 分钟前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5
渊兮兮16 分钟前
Vue3 + TypeScript +动画,实现动态登陆页面
前端·javascript·css·typescript·动画
一棵开花的树,枝芽无限靠近你27 分钟前
【PPTist】添加PPT模版
前端·学习·编辑器·html
土豆湿1 小时前
拥抱极简主义前端开发:NoCss.js 引领无 CSS 编程潮流
开发语言·javascript·css
鸽鸽程序猿4 小时前
【前端】CSS
前端·css
学不会•6 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
猫爪笔记12 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
爱上语文14 小时前
HTML和CSS 表单、表格练习
前端·css·html
小肚肚肚肚肚哦15 小时前
盘点浏览器盒模型中各种 width、height 、边距和位置属性
css·html