V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
rabbbit
V2EX  ›  问与答

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

  •  1
     
  •   rabbbit · 2019-05-03 22:49:41 +08:00 · 1702 次点击
    这是一个创建于 2022 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这篇帖子是 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>
    

    最终结果很有趣:

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

    1.png

    不定宽元素

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

    例如:

    绝对定位元素

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

    • 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 中, 这些名词有不同的叫法:

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

    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? min.png

    如上图所示, 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.
    muti-min.png

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

    根据如上的两个 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 的结果:
    chrome.png
    Firefox 的结果:
    firefox.png
    值得一提的是, img 元素的宽度计算了两次.
    第一次计算发生在 container 的宽度确定之前,用于确定 container 的宽度.
    第二次计算发生在 container 的宽度确定之后,用于确定 img 自身的宽度.

    5 条回复    2019-05-05 05:14:50 +08:00
    rabbbit
        1
    rabbbit  
    OP
       2019-05-03 22:53:29 +08:00
    pinews
        2
    pinews  
       2019-05-04 03:06:26 +08:00
    @rabbbit 非常感谢你详细的讲解,把一件一般没人注意到的地方研究透彻,我虽然提出了疑问,却没有自己解决,很惭愧。
    具体计算方式还有待消化,但是仿佛看到一个新的大门
    Exin
        3
    Exin  
       2019-05-04 13:58:48 +08:00 via iPhone
    有点意思 请问能否介绍一点系统地了解这些计算逻辑的资料?
    rabbbit
        4
    rabbbit  
    OP
       2019-05-05 05:05:33 +08:00
    @Exin 据我所知没有,一般都是讲表现顺便加带点.
    rabbbit
        5
    rabbbit  
    OP
       2019-05-05 05:14:50 +08:00
    @Exin 想了解这方面的知识只能去啃 w3c 文档, 结合着文档和表现 /规则一起讲的文章很少.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3227 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 10:45 · PVG 18:45 · LAX 02:45 · JFK 05:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.