1. 不定宽元素
  2. shrink-to-fit(收缩至适应宽度)
  3. 实际宽度的计算方式
  4. Firefox的计算方式
  5. 参考

不定宽元素的宽度计算

本文所讨论的中心点为: 如果"不定宽元素"内有"宽度为百分数的 img", 浏览器将如何计算容器和 img 的实际宽度.

注意:
1 除非提及,本文所有代码以 Chrome75 的效果为准.
2 本文中的"不定宽元素"是一个自造词,详见"不定宽元素"一节.
另外,本文是 stackoverflow 上这个讨论的总结,推荐直接去看原文.

举例:
有一个浮动元素(以下记为container):

1
<div class="container" style="float: left"></div>

container 中有一些图片, 当图片的 width 值为百分比时,浏览器如何计算 container 和 img 的宽度?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
margin: 0;
font-size: 0;
}
</style>
</head>
<body>
<div id="container" style="position:absolute;">
<!-- #img1的宽度为300x200px -->
<img id="img1" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
<!-- #img2的宽度为200x100px -->
<img id="img2" src="https://i.imgur.com/Ed0juok.jpg" style="width: 50%" />
</div>
</body>
</html>

最终结果很有趣:

  • #container 的实际宽度为 500px
  • #img1 的实际宽度为 500px
  • #img2 的实际宽度为 250px

1.png

不定宽元素

注意: 本文中的"不定宽元素"是一个自造词,用来代指其宽度由内容决定的元素.

例如:

绝对定位元素

1
<div style="position:absolute;"></div>

内联块元素

1
<div style="display:inline-block;"></div>

浮动元素

1
<div style="float:left;"></div>

shrink-to-fit(收缩至适应宽度)

如果未声明元素宽度(width: auto),部分元素会使用 shrink-to-fit width 作为元素的实际宽度.

If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
如果没有指明宽度,则使用 shrink-to-fit width 计算实际的宽度值.

CSS2/visudet/zh-hans - HTML5 Chinese Interest Group Wiki

shrink-to-fit width的计算方式如下:

shrink-to-fit width = min(max(preferred minimum width, available width), preferred width)

以下元素会采用 shrink-to-fit width 来计算实际宽度:

  • Floating, non-replaced elements: 浮动,非替换元素(float)
  • Absolutely positioned, non-replaced elements: 绝对定位,非替换元素(position: absolute|fixed)
  • 'inline-block', non-replaced elements in normal flow: 内联块,非替换元素(display: inline-block)

另外在 CSS3 中, 这些名词有不同的叫法:

CSS2 CSS3 CSS3 对应的样式
preferred minimum width min-content size width: min-content
preferred width max-content size width: max-content
available width stretch-fit size width:-webkit-fill-available
shrink-to-fit width fit-content size width: fit-content

fit-content size 的计算公式如下,其实和 CSS2 一样:

fit-content size = min(max-content size, max(min-content size, stretch-fit size))

CSS Intrinsic & Extrinsic Sizing Module Level 3

实际宽度的计算方式

通常来讲, 样式 width: <precentage> 的实际宽度相对于 containing block (包含块)计算.
但在某些情况下, 包含块的宽度由子元素决定,子元素的宽度又依赖包含块.

例如:
浮动元素 #container 内有一个 #img.
因为 float,container 有多宽取决于内部的 img.
但是 img 的 width 值是个百分数, 也就是 img 有多宽取决于外部的 container.
如此下去,就变成死循环了.

1
2
3
4
5
<style> body {margin: 0;} </style>
<div id="container" style="float: left">
<!-- img的原始尺寸为300x200 -->
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
</div>

为了解决这个问题, CSS3 是这样做的:

When calculating the containing block’s size, the percentage behaves as auto.
忽略百分比的 width 值, 当作 width: auto 来处理.

CSS Intrinsic & Extrinsic Sizing Module Level 3

相当于:

1
2
3
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
-->
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" />

也就是,根据"图片的原始宽度"(300px)来计算 #container 的宽度.

这里又有了一个新问题.
上文提到了不定宽元素的宽度计算公式是: min(max(preferred minimum width, available width), preferred width). 那么, 这个"图片的原始宽度"(300px)会被带入到 preferred minimum width | available width | preferred width 这三个值中的哪个呢?
(本文假设 body 的宽度 width 为 1366px)

available width 指的是外层包含块的宽度, 即#container的父元素body的宽度(1366px). 因此排除available width:

1
min(max(preferred minimum width, 1366), preferred width)

preferred minimum width 和 preferred width 不太好判断,所幸 CSS3 提供了 min-content 和 max-content.
通过这两个属性值,可以直接得到 preferred minimum width 和 preferred width 的结果.
下面写两个demo看一下

preferred width:

1
2
3
4
<div id="container" style="width: max-content">
<!-- img的原始尺寸为300x200 -->
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
</div>

preferred width的结果如下(300px),和图片的原始宽度一致.
max.png

preferred minimum width:

1
2
3
4
<div id="container" style="width: min-content">
<!-- img的原始尺寸为300x200 -->
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
</div>

preferred minimum width的结果如下, 是0? min.png

如上图所示, preferred minimum width 的结果非常奇怪.所以我又写了几个测试:
1 先多加几个图片试试看

1
2
3
4
5
6
<div id="container" style="width: min-content">
<!-- img的原始尺寸为300x200 -->
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
</div>

结果如下图,还是0.
muti-min.png

2 加个字母进去试试

1
2
3
4
5
6
7
<div id="container" style="width: min-content">
<!-- img的原始尺寸为300x200 -->
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%" />
aaaaaa aaaaaa
</div>

可以看到,这回图片能显示了.并且 #container 的宽度为其内部"最长单词的宽度".
muti-letter.png

根据如上的两个 demo 可以假设, 在这种特定的情况下(不定宽元素内存在百分比宽度的img). Chrome 在计算 #container 元素的 preferred minimum width 时,忽略了内部"百分比宽度的" img, 将这些图片的宽度当作 0 来处理.

因此,#container元素的fit-content size为300px:

1
min(max(0, 1366), 300)  = 300

Firefox的计算方式

Chrome将图片的原始宽度作为 preferred width 处理,不考虑其高度(height),但是 Firefox 不同.
Firefox 会先计算 img 的高度(height), 然后根据图片原始的宽/高的比例来计算 preferred width.

例如如下这段代码:

1
2
3
4
<div id="container" style="float: left">
<!-- img的原始尺寸为300x200 -->
<img id="img" src="https://i.imgur.com/fH2hTRa.jpg" style="width: 100%;height: 100px" />
</div>

图片元素的尺寸为 300x200,比例大约是 1.5:1.
忽略 width 后, 火狐会根据height(高度)和比例值计算宽度,也就是 100 * 1.5 = 150.
因为, 图像的最终宽度为 150px.

Chrome 的结果:
chrome.png
Firefox 的结果:
firefox.png
值得一提的是, img 元素的宽度计算了两次.
第一次计算发生在 container 的宽度确定之前,用于确定 container 的宽度.
第二次计算发生在 container 的宽度确定之后,用于确定 img 自身的宽度.

参考

css - Why are the results of img width different in some browsers? Who is correct? - Stack Overflow
CSS2/visudet/zh-hans - HTML5 Chinese Interest Group Wiki
Visual formatting model details
CSS Intrinsic & Extrinsic Sizing Module Level 3
理解CSS3 max/min-content及fit-content等width值 « 张鑫旭-鑫空间-鑫生活
width - CSS(层叠样式表) | MDN