你是不是也一直以为“CSS 百分比都是相对于父容器计算的”?这句话听起来没毛病,因为这是绝大多数 CSS 教程、文档共同给出的结论。但只要你深入写过几个真实项目,就会碰到一些很诡异的情况:某个 absolute 定位的子元素宽度设成 50%,结果比父元素宽度还宽;某个 height: 50% 的子元素完全不起作用;flex 布局里的子项宽度设了 50%,最后分配结果却不是你想的那样。这些坑让我意识到,CSS 百分比的计算基准远不止“父容器”这一个维度。
这篇文章我想把这几年踩过的坑彻底聊透。我会按元素类型、属性类型、布局模式三个维度拆解百分比的真实计算基准,并配合具体代码和调试思路,帮你建立一套真正可靠的判断模型。
1. 先看基础规则:百分比究竟是“百分比”的谁
我们先从最经典的几个属性入手。width、height、padding、margin、top、left,这 6 个属性是 CSS 里最常写百分比的,但它们各自的参照物,其实早有严格区分。
1.1 width 和 height 的基准差异
width 的百分比计算相对包含块(containing block)的 content-box 宽度。如果父容器的宽度是 800px,padding 是 40px,border 是 4px,那么子元素的 width: 50% 实际计算结果是 (800 * 50%) = 400px,而不是 (800 + 40 + 4) * 50%。这里面有个很容易被忽略的前提:包含块自身宽度是多少,取决于父容器所处的格式化上下文。
height 的百分比就没有这么“爽快”了。它的计算相对包含块的 content-box 高度,但前提是,父容器的高度必须能被确定,否则百分比会直接失效,最终计算值退化为 auto。这个“高度必须能被确定”的含义是,父元素必须有显式 height,或者父元素的 height 可以在渲染过程中被计算出来。举个例子:
css复制.parent {
width: 800px;
height: 300px;
}
.child {
height: 50%;
}
这种情况下,child 的高度是 150px,没问题。但如果父元素只设置了 min-height: 300px,而没有显式 height,那么 height: 50% 在标准流中基本失效,子元素高度直接变成 auto,按内容撑开。
注意:min-height 不等于 height。百分比的 height 计算依赖的是父元素的 height 值,而不是 min-height 或 max-height 的中间结果。这是很多新手布局“高度塌了”的根本原因。
1.2 padding 和 margin 的百分比:一个反直觉的规则
padding 和 margin 的百分比,是 CSS 中最容易翻车的地方。因为它们的百分比基准,既不是父容器的高度,也不是自身高度,而是——父容器的 content-box 宽度。
也就是说,无论你写的是 padding-top、padding-bottom、margin-top、margin-bottom,百分比统统参照父容器宽度计算。这么设计是有历史原因的:在早期 CSS 排版中,CSS 规范作者希望垂直方向的比例能保持稳定。如果 padding-top 的百分比参照父容器高度,那父容器高度一变,整个元素内部的垂直节奏就全乱了。而宽度通常是相对稳定、可预测的,以宽度为基准,能让页面在不同宽度下保持比例协调。
放到实际项目里,这个特性最典型的应用是“定宽高比的占位符”:
css复制.box {
width: 300px;
padding-top: 50%; /* 实际高度 = 300 * 50% = 150px */
}
这种方法常用来做 2:1 的图片占位容器,以防止图片加载时页面布局抖动。理解了 padding 百分比的基准,你就知道为什么它能做出比例稳定的占位区块。
但同样,这个特性也带来了一些坑。曾经我在一个卡片列表里,给卡片设置了 padding-bottom: 30%,本意是让卡片底部留出相对自身高度 30% 的空白,结果 30% 是相对卡片宽度的,卡片在窄屏上一下子被拉得很高。这类问题在移动端适配中尤其常见。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 定位元素的百分比偏移:包含块的重新定义
CSS 里最容易让“百分比等于父容器”落空的场景,就是定位元素,尤其是 absolute 定位。因为 absolute 元素的包含块,不是父元素,而是最近的、position 属性不为 static 的祖先元素。如果祖先里面没有定位元素,那就一路向上找到初始包含块(通常是 html 元素形成的视口区域)。
2.1 absolute 定位中 top/left/right/bottom 的百分比基准
我们来看一个经典例子:
html复制<div class="grand-parent">
<div class="parent">
<div class="child"></div>
</div>
</div>
css复制.grand-parent {
position: relative;
width: 1000px;
height: 500px;
}
.parent {
width: 600px;
height: 300px;
/* 注意:没有设置 position */
}
.child {
position: absolute;
left: 50%;
top: 50%;
}
这种情况下,child 的 left: 50% 依据的是 grand-parent 的宽度,也就是 500px,而不是 parent 的 300px。很多调试半天发现定位元素 “飘” 到了奇怪位置的场景,十有八九就是这个原因:定位元素的包含块,并不是视觉上的父容器,而是最近的定位祖先元素。
更复杂一点的是 right 和 bottom。left: 50% 和 right: 50% 的基准一样,都是包含块的宽度;top、bottom 的基准都是包含块的高度。这里没有悬念,真正的悬念在于这些偏移的组合计算。
2.2 fixed 定位的特殊性:视口与包含块的边界
fixed 定位的包含块,在绝大多数情况下是视口,所以 top: 50% 就是视口高度的 50%。但这里有个例外:如果某个祖先元素设置了 transform、perspective、filter 或 will-change: transform,那么这个祖先元素会成为 fixed 元素的包含块。也就是说,你的 fixed 定位元素突然“不听视口使唤”了,开始参考 transform 祖先来定位。
这个坑很经典,也是很隐蔽的布局bug来源。比如你在弹窗组件外包了一层用于居中动画的容器,这个容器设置了 transform: translateY(-50%),里面的 fixed 子元素就会把该容器当作包含块,原本你要贴住视口底部的结果,可能就飘到容器一半位置去了。这类“意外”的表现,不是百分比规则变形,而是包含块的对象发生了改变。
调试固定定位元素时,第一件事就是检查祖先链上有没有 transform、filter、perspective、will-change 这些属性。这种情况在代码审查阶段几乎看不出问题,必须运行时借助浏览器 DevTools 点选元素,看 Computed 面板里的包含块位置和尺寸,才能快速定位。
2.3 absolute 元素的百分比尺寸与偏移的组合
当 absolute 元素同时设置了 left、right 和 width 的时候,它们之间会互相博弈。CSS 规范在计算绝对定位元素尺寸时有一套优先级:
- 如果 left、width、right 都非 auto,则 right 会被忽略(在 LTR 方向下),width 按包含块宽度算。
- 如果只有 left 和 right,没有 width,则元素宽度会被拉伸到符合 left + right + margin = 包含块宽度的关系。
- 如果只有 width,left/right 为 auto,那元素就待在正常静态位置,而不是靠定位。
这也就意味着,你给 absolute 元素同时设置 width: 50% 和 left: 20% 时,这 50% 是基于包含块宽度的,而不是基于“包含块减去 left 后的剩余空间”。这与 flex 布局中 item 的 flex-basis 计算方式有本质区别。灵活运用这一差异,可以做出自适应宽度的悬浮层。
3. 现代布局里的百分比:flex 与 grid 中的计算基准
接下来是面试里最常考的进阶点:在 flex 和 grid 布局中,百分比宽度不再仅仅由包含块决定,还受到 flex 伸缩算法、网格轨道分配的影响。很多开发者在 flex 布局下修改子元素的 width: 50%,却发现布局效果跟预期完全不一致,就是因为没搞懂 flex-basis 与 width 的叠加关系。
3.1 flex item 的宽度百分比
在 flex 容器里,子项的 width: 50% 这个百分比数值,依然是相对容器宽度的,这个没问题。问题在于,最终渲染宽度不完全等于 50%,因为 flex 属性会参与宽度分配:
- 如果 flex-grow 大于 0,宽度分配时会先按 flex-basis(或 width)的数值计算“假设宽度”,然后根据剩余空间分配增长量。比如容器宽度 1000px,三个 item 都设了 width: 50%,flex-grow 都是 1,那么每个 item 的基准宽度都是 500px,总基准 1500px,超出容器,实际会被压缩到均分容器宽度,也就是各 333px。
- 如果子项包含不可压缩内容(比如很长的英文单词),min-width: auto 规则会导致实际宽度被内容撑大,即使你写死了 width: 10%。
所以你在 flex 布局下看到子项宽度不符合 width 百分比,首先不要怀疑百分比基准变了,而是先检查 flex-grow、flex-shrink、flex-basis 是不是动了手脚。
3.2 flex-basis 与 width 的优先级
flex-basis 的优先级高于 width(当 flex-basis 不为 auto 时)。假如你写了:
css复制.item {
flex: 1 1 50%;
width: 30%;
}
那么 flex-basis: 50% 会代替 width: 30% 作为基准宽度,最后 item 的初始假设宽度是容器宽度的 50%,而不是 30%。这个细节我见过无数人踩坑。有些同学把 width 设为 100%,以为子项能占满整行,但 flex-basis 还是默认的 auto,结果因为 flex-shrink 的存在,子项被压缩得少了一块。
反过来利用这个特性,你可以用 flex-basis 来控制子项的初始宽度,再配合 flex-grow 来分配剩余空间。比如如下代码可以实现三列等宽布局:
css复制.container {
display: flex;
}
.item {
flex: 1 1 0%; /* flex-basis 为 0,所有宽度都靠 grow 分配 */
}
这种写法,width 的百分比就作用不到了,因为 flex-basis 已经把基准宽度设置为 0。
3.3 grid 轨道尺寸的百分比
grid 布局的百分比相对对象,跟 flex 布局又不一样。当你定义:grid-template-columns: 50% 1fr 50% 时,这 50% 的基准是 grid 容器的 content-box 宽度,不是可用空间的分数。而 1fr 是 fr 单位,它分配的是“剩余可用空间”,而不是百分比轨道之外的剩余。需要注意,如果 grid 容器设置了 padding 和 gap,百分比轨道的计算会略复杂。
此外,grid item 内部的 width: 100% 相对的是它的网格区域(grid area),不是整个 grid 容器。如果你想把某个 item 强制拉伸到跨两列的宽度,直接设 width: 100% 其实没什么意义,它只会填满它所在的网格区域,而不是视觉上的整行。这种时候你会更明显感受到,CSS 百分比不仅仅是“父容器”的宽度,而是“包含块/网格区域”的内部宽度。
3.4 flex 子项百分比和 grid 子项百分比的实际测试
为了验证上述规则,我在本地跑过一个对比实验:
html复制<div class="flex-container">
<div class="flex-item" style="width: 50%">A</div>
<div class="flex-item" style="width: 50%">B</div>
</div>
css复制.flex-container {
display: flex;
width: 800px;
}
.flex-item {
flex: 0 0 auto;
}
这种情况下,两个 item 的实际宽度都是 400px,符合预期。当我加上 flex: 1 1 auto 后,两个 item 的实际宽度会变成 450px、450px(按 1:1 分配剩余空间)。这组对比清晰地展示了 flex 属性对百分比的“修正”作用。
grid 那边的实验就更有意思了:
css复制.grid-container {
display: grid;
grid-template-columns: 50% 1fr;
width: 1000px;
}
第一列会被解析为 500px,第二列 fr 分掉剩余 500px。但如果我给 grid 容器加上 padding: 50px,并且 box-sizing 是 border-box,那么 grid 容器的 content-box 宽度就是 900px,第一列 50% 实际是 450px,fr 会分掉剩下的 450px。这说明,百分比不仅仅是“相对包含块宽度”,还要考虑盒模型引发的 content-box 尺寸变化。
4. transform 百分比:相对自己的坐标系
前面聊的百分比都是相对于外部容器或包含块,唯独 transform 里的百分比,是相对于自身元素尺寸的。这是一个非常容易忽略的差异。
4.1 translate 百分比是自身的 50%
比如:
css复制.box {
width: 200px;
height: 100px;
transform: translateX(50%);
}
这里 translateX(50%) 计算的是自身宽度的 50%,也就是 100px,而不是父容器的 100px。这个规则很关键,因为很多垂直水平居中方案就依赖它:
css复制.centered {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
这里的 left: 50% 和 top: 50% 是相对于包含块的尺寸,而 translate(-50%, -50%) 是相对于自身尺寸。两者共同作用下,元素精确居中。如果有天你发现某个“居中”元素偏了,多半是包含块宽高不确定,或者 transform 被覆盖了。
4.2 scale 和 rotate 里的百分比问题
scale 的百分比比较特殊,比如 scale(50%) 实际上是 0.5 倍缩放,这里的百分比不严格是 50% 尺寸,而是规范定义为 scale 参考“自身对应轴长度”。但 rotate 的百分比用得极少,因为旋转没有“百分比”概念。大多数情况下,transform 中的百分比只出现在 translate 和 scale。
transform-origin 属性也支持百分比,默认值是 50% 50%,也就是元素自身的中心点。这个百分比同样是相对于自身 border-box 尺寸。如果元素尺寸未知,用百分比原点可能会产生不可预期的旋转中心。
4.3 在动画和交互中的实际运用
正因为 transform 百分比参照自身尺寸,我们才能实现一些“不依赖容器宽度”的效果。比如做一个悬停放大的效果:
css复制.card {
transform: scale(1);
transition: transform 0.3s;
}
.card:hover {
transform: scale(1.05);
}
这里 1.05 是倍数,不涉及百分比。但如果想实现“卡片中心不动,边缘扩展”的效果,配合 transform-origin: 50% 50% 会稳定很多。
还有一种场景是拖拽交互里的吸附效果:你想要元素在移动时,始终参考自身一半位置来对齐某个点。用 translate(-50%, -50%) 非常顺手,因为无论元素宽高怎么变,它总能保证中心对齐到指定坐标。如果你用 left: 50%; margin-left: -100px 这种写法,一旦元素宽度变化,就要同步修改 margin-left,非常痛苦。
4.4 transform 百分比与父容器百分比的混用陷阱
混用时要格外注意,因为它们属于完全不同的坐标系。一个典型的错误是:
css复制.popup {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) scale(0.8);
}
这看起来是居中后缩小 0.8 倍。但注意,scale 会在 translate 之后执行,而 transform 变换的顺序是右到左,所以实际执行顺序是:先 scale(0.8),再 translate(-50%, -50%)。由于顺序不同,元素中心点可能会偏。这种视觉 bug 验证起来很麻烦,因为浏览器 DevTools 显示的 transform 矩阵是合并后的,调试时需要手动模拟每一步的矩阵计算,才能知道中心点到底偏移了多少。
经验是:如果需要先缩放再居中,可以把 scale 放在 translate 前面:
css复制.popup {
transform: scale(0.8) translate(-50%, -50%);
}
不过这种写法会让百分比 translate 先按照原尺寸计算,还是按照缩放后的尺寸?答案是原尺寸,因为百分比是基于元素原始边界框尺寸计算的,不受 transform 影响。这一点是很多人调 bug 调半天也找不到原因的地方。
5. 盒模型属性里的百分比细节:border-radius 与背景位置
除了上面几个高频属性,还有一些属性的百分比基准容易混淆,尤其是 border-radius 和 background-position。它们不常写百分比,但一旦写复杂效果,就会踩中一个隐蔽坑:百分比参考的对象可能是自身,也可能是包含块。
5.1 border-radius 百分比:相对自身宽度和高度的椭圆半径
border-radius 的百分比是相对于元素自身 border-box 的宽度和高度。所以一个 400x200 的元素,设置 border-radius: 50% 会生成一个椭圆,横向半径 200px,纵向半径 100px。如果你想要正圆,必须保证元素本身是正方形,或者使用固定像素值(比如 999px)。
这个特性在做头像、按钮圆角时很常用。比如一个宽度不固定的胶囊按钮,要永远保持两端是半圆,可以设置 border-radius: 999px,而不是 50%,因为 50% 在非正方形元素上会拉出椭圆效果。
有意思的是,border-radius 百分比对单个角也有效。比如 border-top-left-radius: 50% 20%,第一个值是水平半径,相对宽度;第二个值是垂直半径,相对高度。在做一些不规则圆角卡片时,这种写法能实现很柔和的造型,但代价是理解成本高。
5.2 background-position 百分比:不是你想的“位置比例”
background-position 的百分比计算规则非常反直觉。如果是 background-position: 50% 50%,大家都会认为是背景图中心对齐容器中心,这个直觉正确。但换成 background-position: 20% 30% 的时候,它并不是说背景图左上角放在容器 20% 30% 的位置,而是:
图片上距离左上角 20% 宽度的点,与容器上距离左上角 20% 宽度的点对齐。
具体计算公式是:
code复制(容器宽度 - 图片宽度) * 百分比 = 图片左边缘到容器左边缘的距离
如果容器和图片尺寸一样,无论百分比是多少,背景图都不会移动,因为 (容器宽度 - 图片宽度) = 0。这个公式初看很怪,但其实它保证了背景图始终在容器内保持“相对位置一致”,而不是简单地按比例偏移。
实际项目中,我会用 background-position: 100% 100% 让背景图右下角对齐容器右下角,这样在响应式页面里做“角落标牌”效果非常方便。但如果你想用百分比做滚动的视差背景,就需要换用 fixed 定位或 transform,百分比在这种场景下并不好用,因为它依赖图片和容器的具体尺寸差。
5.3 渐变里的百分比和 border-image 等冷门场景
CSS 渐变里的百分比,是指渐变停止色的位置相对背景区域尺寸的百分比。例如 linear-gradient(to right, red 0%, blue 100%),0% 对应背景区域左边缘,100% 对应右边缘。这里的背景区域,受 background-origin 和 background-size 影响。如果 background-size 不是 100%,那百分比就相对“背景图片区域”而不是整个元素,容易造成渐变色位置偏移,这个也建议调试时留意。
border-image 的 slice 百分比,是相对边框图片本身尺寸的,不是相对元素。它更冷门,平时极少写百分比。总之,遇到这些属性时,先确认“百分比参考对象”是自身尺寸、包含块宽度,还是背景区域尺寸,再往下调。
6. 视口单位与百分比的分工:什么时候该用 vw/vh,什么时候用百分比
看到这里你应该发现,百分比的计算基准依赖上下文,复杂且多变。所以在做响应式布局时,我经常在百分比与视口单位(vw、vh)之间做选择。两者不是替代关系,而是不同场景下的工具。
6.1 vw/vh 的基准就是视口,简单直接
vw 是视口宽度的 1%,vh 是视口高度的 1%。这个规则没有包含块、没有父元素、没有自身尺寸,就是浏览器可视区域。写一个全屏背景层:
css复制.hero {
width: 100vw;
height: 100vh;
}
效果是整个背景铺满视口,即使父元素有 padding 也不影响。但注意,100vw 会把滚动条宽度也计算进去,在 Windows 等滚动条常驻的系统上,页面可能出现横向溢出。更稳的写法是 width: 100%,配合 body 的默认 margin 清零。
vmin 和 vmax 分别是视口较小边、较大边的 1%。在做适配手机横竖屏的元素时,能省不少事。比如一个始终不超出屏幕的正方形:
css复制.square {
width: 50vmin;
height: 50vmin;
}
这个写法相当于“在手机竖屏时以宽度为基准,横屏时以高度为基准”,保证元素始终占屏幕较小边的一半。
6.2 百分比是“相对包含块”,vw/vh 是“相对视口”
在组件化开发里,我更倾向在组件内部使用百分比,让组件的尺寸随着父容器变化;只有在全屏广告、首屏大图、弹窗遮罩这类明确要“占满整个视口”的场景里,才会用 vw/vh。
举个例子,一个模态框,如果希望它在不同尺寸屏幕上永远不超过视口的 90% 高度,可以这样:
css复制.modal {
max-height: 90vh;
width: 80%;
}
这里的 max-height: 90vh 保证弹出层不会超出屏幕,而 width: 80% 让它相对父容器自适应。百分比和视口单位结合起来,既照顾了父子关系,又限制了大屏溢出的风险。
6.3 动态视口单位 dvh/svh/lvh 与百分比的适配
移动端浏览器地址栏显隐会改变视口高度,这导致 vh 在一些场景下表现不稳定。于是出现了 svh(小视口高度)、lvh(大视口高度)、dvh(动态视口高度)。dvh 会随地址栏显隐动态变化,更适合移动端全屏布局。
我给移动端页面做底部操作栏时,常用:
css复制.bottom-bar {
height: calc(100dvh - 60px);
}
这里的 100dvh 比 100vh 更精准,因为地址栏收起时 dvh 会自动变大,操作栏不会被顶出屏幕。百分比在这种场景下帮不上忙,因为父容器(body)的高度本身取决于视口,写 height: 100% 还得先保证 html 和 body 都有 height: 100%,而 dvh 直接绕开了这一层。
7. 实战排查方法论:如何在调试时快速判断百分比基准
最后,我把自己平时排查百分比问题的思路整理成一套固定的调试流程。遇到任何“百分比跟预期不符”的布局,按下面几步走,基本都能定位。
7.1 第一步:确定包含块
在 DevTools 的 Elements 面板选中目标元素,查看 Computed 面板,找到 “Containing Block” 相关提示(部分浏览器会标注,或者通过 Layout 面板查看)。如果没有,可以手动往祖先链上找 position: relative/absolute/fixed 的元素,以及 transform、perspective、filter、will-change。包含块一旦确定,百分比的基准就明确了 80%。
7.2 第二步:检查父容器是否有显式尺寸
如果是 height 百分比失效,重点检查父容器的 height 是否可确定。如果父容器只有 min-height 或 max-height,那 height 百分比大概率失效。如果是绝对定位元素的 top/bottom 百分比没生效,检查是不是祖先里没有定位元素,导致参考了初始包含块(视口)。
7.3 第三步:检查布局模式的干扰
在 flex 容器里看 width 百分比,先关掉 flex-grow、flex-shrink 的干扰,把 flex 设置为 0 0 auto 再观察。在 grid 容器里,确认百分比轨道是否受 gap 和 padding 影响。通常我会在 DevTools 里临时给目标元素加一个 outline,并注释掉 flex 相关属性,一步步确认变化。
7.4 第四步:用 outline 和临时色块可视化工具体系
调试百分比布局,我不会依赖计算器,常直接在元素上加:
css复制.debug {
outline: 2px solid red;
background: rgba(255, 0, 0, 0.1);
}
在目标元素和父元素、包含块上分别加不同颜色的描边,用颜色区分关系。这样能非常直观地看到“我认为的父元素”和“实际的包含块”之间的差异。
下面是我常用的一份 debug 附加 CSS:
css复制/* 调试用,发布前删除 */
[data-debug="containing"] { outline: 4px solid blue; }
[data-debug="parent"] { outline: 2px dashed green; }
[data-debug="target"] { background: rgba(255,0,0,0.2); }
配合 data 属性,可以快速分离出三者的尺寸和位置关系。如果再配合 DevTools 的 Layout 面板,打开 “Show rulers” 和 “Show element sizes”,就能精确看到元素计算的 px 值。
7.5 一个完整的排查案例
之前我调试过一个问题:一个二级菜单,position: absolute,left: 50%,本来想让菜单出现在父按钮的正下方居中,结果菜单整体偏到了页面右侧。排查流程如下:
- 选中菜单元素,看到 left: 50%,计算值是 640px。父按钮宽度是 200px,它左边缘距离页面左侧 300px,按理说 50% 应该是 100px,菜单左边缘应该出现在 300+100 = 400px 处。
- 但实际计算值是 640px,说明它参考的包含块不是父按钮。
- 往上找祖先元素,发现菜单的外层容器设置了 transform: translateY(0),本来是想触发 GPU 合成,结果它导致包含块变成了这个容器。
- 去掉这个 transform 后,菜单定位恢复正常。最后把动画方式改成 opacity,避免影响定位。
这让我深刻认识到:包含块的判断,必须作为定位元素百分比问题的第一排查步骤。
7.6 关于调试工具的补充建议
除了 DevTools,我还会在 CSS 里使用 CSS 自定义属性辅助调试。比如把包含块的预期宽度、实际宽度都记录为变量:
css复制:root {
--debug-expected-width: 200px;
--debug-actual-width: 640px;
}
然后用伪元素的 content 打印出一些关键值,方便截图时直接比对。虽然有点土,但在些复杂组件联调现场非常管用。
8. 总结一下我对 CSS 百分比的完整判断模型
把前面梳理的规则压缩成一张决策表,方便你日常速查:
| 属性 | 百分比基准 | 关键条件 |
|---|---|---|
| width | 包含块 content-box 宽度 | 包含块宽度可确定 |
| height | 包含块 content-box 高度 | 父容器或包含块高度必须显式确定 |
| padding (top/bottom/left/right) | 包含块 content-box 宽度 | 与自身方向无关,全部参照宽度 |
| margin (top/bottom/left/right) | 包含块 content-box 宽度 | 同上 |
| absolute 的 left/right | 包含块宽度 | 包含块是最近的定位祖先或 transform 祖先 |
| absolute 的 top/bottom | 包含块高度 | 同上 |
| fixed 的 left/top 等 | 视口 | 但 transform/filter/perspective 祖先会改变包含块 |
| flex-basis | 主轴方向的弹性容器尺寸 | flex-basis 优先级高于 width |
| grid-template-columns | grid 容器 content-box 宽度 | 受 gap、padding 影响 |
| transform: translate | 自身尺寸 | 不受父容器影响 |
| transform-origin | 自身 border-box 尺寸 | 默认 50% 50% |
| border-radius | 自身 border-box 宽高 | 非正方形时 50% 为椭圆 |
| background-position | (容器尺寸 - 图片尺寸) * 百分比 | 公式计算出的偏移量 |
| 渐变色标位置 | 背景区域尺寸 | 受 background-size/origin 影响 |
| vw/vh/dvh/svh | 视口 | 移动端注意地址栏显隐导致 vh 变化 |
这张表基本覆盖了日常开发中所有会写百分比的场景。我的经验是,不要死记硬背每个属性的基准,只需要记住一句核心判断原则:看到百分比,先问三层问题——第一层,这个属性的百分比参考自身还是外部?第二层,如果参考外部,是包含块还是父容器还是视口?第三层,包含块有没有被 transform 等属性偷梁换柱?
在真实项目里,我更建议把“百分比基准”当成和盒模型、层叠上下文一样的底层思维,写每个样式前都在脑子里走一遍这三层问题。遇到复杂布局,宁可多写几个带语义的自定义属性,也不要让百分比嵌套得毫无逻辑。毕竟,CSS 的百分比机制设计得并不统一,一旦项目变大、组件复用变多,一个隐蔽的基准错误,能让你在 DevTools 里耗掉一整个下午。
