V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xfn
V2EX  ›  前端开发

前端大佬帮忙看一下这个关于 img 设置 max-width 后的行为问题

  •  
  •   xfn · 288 天前 · 697 次点击
    这是一个创建于 288 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问题在 so 上也问过( https://stackoverflow.com/questions/78135768/behavior-of-img-width-when-max-width-is-set ),但好像回复都没有抓住我想问题的要点(也可能是我没表述清楚),在这里请 v2 的前端大佬帮忙看下。

    问题是,我有一个 ul ,里面的 li 包含了一个 img 和一个 span (相当于一个 icon 和一个 label ,img 显式设置了宽度和高度),li 设置为了 display:flex 。而这个 ul 本身又在一个 div 下面,并且这个 div 的宽度可能不足以容纳 ul 的内容。如果 div 的宽度比 ul 窄,同时如果 li 里面的 img 设置了 max-widht:100%,则 li 就不能完全容纳这个 img 和 span ,span 的内容会超出 li 一部分。但如果把 img 的 max-width 去掉,或者把 img 换成 div (即便加上 max-width:100%),li 就能容纳元素的宽度。

    我的问题是,既然在 flexbox 容器下 img 和 div 都是 block 元素(用浏览器 devtools 看到的结果),为什么行为会不一样?是什么样的 css 规则导致了这种不一致?

    代码效果可以在这里看到: https://jsfiddle.net/c508dz6o/1/

    <div class="root">
      <div class="container">
        <ul class="list-container">
          <li>
            <img class="icon" src="https://jsfiddle.net/img/favicon.png" />
            <span>Hello World !!!</span></li>
          <li>
            <div class="icon"></div><span>JavaScript</span>
          </li>
          <li>
            <div class="icon"></div>
            <span>Rust</span>
          </li>
        </ul>
      </div>
    </div>
    
    
    * {
      border: 0px;
      box-sizing: border-box;
      margin: 0px;
      padding: 0px;
    }
    
    .root {
      display: flex;
      justify-content: center;
    }
    
    .list-container {
      border: 1px solid black;
      padding: 24px;
      list-style:none;
      display: flex;
      flex-direction: column;
    }
    
    .list-container li {
      border: 1px solid green;
      margin-top: 10px;
      white-space: nowrap;
      display: flex;
      align-items: center;
    }
    
    .icon {
      background: green;
      width: 16px;
      height: 16px;
      max-width: 100%;
      margin-right: 14px;
    }
    
    .list-container li:first-child {
      margin-top: 0;
    }
    
    .container {
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 10px;
      width: 50px;
    }
    
    第 1 条附言  ·  287 天前

    我把这个问题的示例简化了一下在这里 https://jsfiddle.net/o3dgjf0s/6/ 只需要关注 item 这个 div 中的元素就行了。

    <div class="root">
       <div class="container">
          <div class="item">
             <img class="icon" />
             <!-- <div class="icon"></div> -->
             <span>Hello World !!!</span>
          </div>
       </div>
    </div>
    
    * {
      border: 0px;
      box-sizing: border-box;
      margin: 0px;
      padding: 0px;
    }
    
    .root {
      display: flex;
      justify-content: center;
    }
    
    .item {
      border: 1px solid black;
      display: flex;
      align-items: center;
      white-space: nowrap;
    }
    
    .icon {
      background: green;
      width: 16px;
      height: 16px;
      max-width: 100%;
      flex-shrink: 0;
      overflow: hidden;
    }
    
    .container {
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 10px;
      width: 50px;
    }
    
    
    6 条回复    2024-03-12 19:22:35 +08:00
    snarkprayer
        1
    snarkprayer  
       287 天前
    大概是 img 的默认 overflow:clip 导致的,hello word 禁止换行,所以 img 会被挤压,但因为 clip 所以挤压失效了,而父元素宽度是计算挤压后的,所以看起来文字超出了 li 元素,你把图片的 overflow 换成 auto 或者 hidden, flex-shrink 也改个值就能看出来区别了
    xfn
        2
    xfn  
    OP
       287 天前
    @snarkprayer 谢谢大佬提供思路。不过好像不是 overflow:clip 的原因,设为 hidden 后行为还是一样的。我把这个问题的示例简化了一下在这里 https://jsfiddle.net/o3dgjf0s/6/,只需要关注 item 这个 div 中的元素就行了。

    ```
    <div class="root">
    <div class="container">
    <div class="item">
    <img class="icon" />
    <!-- <div class="icon"></div> -->
    <span>Hello World !!!</span>
    </div>
    </div>
    </div>

    * {
    border: 0px;
    box-sizing: border-box;
    margin: 0px;
    padding: 0px;
    }

    .root {
    display: flex;
    justify-content: center;
    }

    .item {
    border: 1px solid black;
    display: flex;
    align-items: center;
    white-space: nowrap;
    }

    .icon {
    background: green;
    width: 16px;
    height: 16px;
    max-width: 100%;
    flex-shrink: 0;
    overflow: hidden;
    }

    .container {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 10px;
    width: 50px;
    }

    ```
    chnwillliu
        3
    chnwillliu  
       286 天前
    可以进一步简化 demo ,不需要嵌套 flex 就可以复现。

    https://jsfiddle.net/zc9vqdn6/2/


    ```
    <div class="item">
    <img class="icon" />
    <span>Hello World !!!</span>
    </div>

    <style>
    .item {
    border: 1px solid black;
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
    width: min-content;
    }

    .icon {
    background: green;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    max-width: 100%;
    }
    </style>
    ```
    xfn
        4
    xfn  
    OP
       286 天前
    @chnwillliu 谢谢大佬,简化后直观多了。所以这是啥原因造成的呢?😂
    chnwillliu
        5
    chnwillliu  
       286 天前   ❤️ 1
    个人感觉应该跟这个 section 有关 https://www.w3.org/TR/css-sizing-3/#cyclic-percentage-contribution

    If the box is replaced, a cyclic percentage in the value of any max size property or preferred size property (width/max-width/height/max-height), is resolved against zero when calculating the min-content contribution in the corresponding axis.

    尝试来解释一下:

    前提知识点:div 和 img 的 display 计算值虽然都是 block ,但是 CSS 内部还是区别对待 img 的,因为它是 replaced element 。

    套多层 flex 起到的效果和 width:min-content 一样,简单说就是这个 container 的宽度由子元素 的 min-content 来贡献。虽然 .icon 已经有明确的 width 定义,但它的 max-width 也会影响它的 min-content 最终是多少,而 max-width 如果是百分值,也就是它需要先知道父容器( containing block )的 size ,所以这里就产生了循环依赖。CSS Box Sizing Module Spec 就规定了这种情况,解法分 replaced element 和 non-replaced element 。

    non-replaced element 在计算 min-content / max-content 内在盒子大小时,遇到百分比或循环依赖值,直接就把整个值当作是没定义一样,即,使用其 initial value 来计算。div 的 max-width initial value 是 auto ,width:16px + max-width:auto 得到的 min-content 就是 16px 。

    replaced element 在计算 max-content 时也是一样,但计算 min-content 时不同,循环依赖值会直接当成 0 来对待。width:16px + max-width: 0 得到的 min-content 就是 0 ,所以在父容器计算宽度时,img 贡献了 0 。

    这里的 0 / auto 只会影响解盒子的 min/max-content 的流程,盒子本身的 sizing 过程百分比依然会被遵守,即,max-width:100% 在以 0 对待并算完父容器的宽度后再以百分比算出其值然后作用于 img 上。span 中的字符总宽度 hello word!!! 是 111.25px ,因此含 img 的 .item 算得宽度 111.25px ,img 最终得到 max-width:111.25px width:16px 宽度仍然是 16px 。

    img 加 max-width: 100% 后在贡献宽度的时候贡献了 0 , 在分配宽度的时候还占 16px ,所以整体 size 就不够分了,没人 flex-shrink 所以就溢出了。
    xfn
        6
    xfn  
    OP
       286 天前
    @chnwillliu 先赞,感觉就是这个原因,具体规则再慢慢消化一下
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5271 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 05:46 · PVG 13:46 · LAX 21:46 · JFK 00:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.