[CSS]"不定宽元素"的宽度计算

2019-05-03 22:49:41 +08:00
 rabbbit

这篇帖子是 https://www.v2ex.com/t/559626 11 楼问题的进一步讨论.

为什么单开个帖子?
1 V2EX 的回复不支持 Markdown
2 个人觉得这个回答还是有问题.这里人才多,如果哪里搞错了欢迎指出.

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

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

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

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

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

<!DOCTYPE html>
<html lang="en">
  <head>
    <style>
      body {
        margin: 0;
        font-size: 0;
      }
    </style>
  </head>
  <body>
    <div id="container" style="position:absolute;width: max-content;">
      <!-- #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>

最终结果很有趣:

不定宽元素

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

例如:

绝对定位元素

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

内联块元素

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

浮动元素

<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 来计算实际宽度:

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

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.
如此下去,就变成死循环了.

<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

相当于:

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

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:

<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),和图片的原始宽度一致.

preferred minimum width:

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

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

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

2 加个字母进去试试

<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 的宽度为其内部"最长单词的宽度".

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

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

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

Firefox 的计算方式

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

例如如下这段代码:

<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 的结果:

Firefox 的结果:

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

1717 次点击
所在节点    问与答
5 条回复
rabbbit
2019-05-03 22:53:29 +08:00
pinews
2019-05-04 03:06:26 +08:00
@rabbbit 非常感谢你详细的讲解,把一件一般没人注意到的地方研究透彻,我虽然提出了疑问,却没有自己解决,很惭愧。
具体计算方式还有待消化,但是仿佛看到一个新的大门
Exin
2019-05-04 13:58:48 +08:00
有点意思 请问能否介绍一点系统地了解这些计算逻辑的资料?
rabbbit
2019-05-05 05:05:33 +08:00
@Exin 据我所知没有,一般都是讲表现顺便加带点.
rabbbit
2019-05-05 05:14:50 +08:00
@Exin 想了解这方面的知识只能去啃 w3c 文档, 结合着文档和表现 /规则一起讲的文章很少.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/560767

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX