Search code examples
cssgoogle-chromefirefoxflexboxcss-grid

The relative unit (percentage) cannot be applied by the browser to the space set by fr


Problem details

I wrote code like this. Here, the image should be spread by the space reserved by fr because thewidth, height, andobject-fit properties work. So I thought the text of the second .item would overflow.

.container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr;
  background: red;
  gap: 10px;
}

img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
<div class="container">
  <div class="item">
    <img src="https://unsplash.it/450/450">
  </div>
  <div class="item">
    <img src="https://unsplash.it/450/450">
    text
  </div>
</div>

In Firefox this causes text to run out,

enter image description here

Not so with Chrome.

enter image description here

Also, wrapping the image in a div element instead of just below the grid item will fix the problem.

.container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr;
  background: red;
  gap: 10px;
}

img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
<div class="container">
  <div class="item">
    <div class="content">
      <img src="https://unsplash.it/450/450">
    </div>
  </div>
  <div class="item">
    <div class="content">
      <img src="https://unsplash.it/450/450">
      text
    </div>
  </div>
</div>

Unrelated but similar articles

I found a question that might be related, but I don't think this problem is related to my question because the source code of this question text works correctly in Firefox and Chrome.

Unrelated but similar bugs

I also looked for related bug tickets, but these were already stated to have been fixed and may not be relevant to my problem.

Quesiton

  1. Is this difference a bug without a bug ticket or undefined behavior?

  2. If this is not an undefined behavior, which is the correct behavior?


Solution

  • If this is not an undefined behavior, which is the correct behavior?

    I would say, Firefox is doing correctly here and Chrome is half correct.

    First, you can reduce your code to the following as grid-template-rows: 1fr is not needed to have the behavior

    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      background: red;
      gap: 10px;
    }
    
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    <div class="container">
      <div class="item">
        <img src="https://unsplash.it/450/450">
      </div>
      <div class="item">
        <img src="https://unsplash.it/450/450">
        text
      </div>
    </div>

    Then you can use any percentage value (either smaller or bigger than 100%) and you will see that chrome will do nothing in the second case where we have text:

    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      background: red;
      gap: 10px;
    }
    
    img {
      width: 100%;
      height: 50%;
      object-fit: cover;
    }
    <div class="container">
      <div class="item">
        <img src="https://unsplash.it/450/450">
      </div>
      <div class="item">
        <img src="https://unsplash.it/450/450">
        text
      </div>
    </div>

    As I explained here you are facing a particular behavior of percentage height. By default percentage height is relative to the explicit height of the containing block and in our case we didn't set any explicit height to our row (1fr isn't explicit). Considering the CSS2 specification we should fail to auto

    Specifies a percentage height. The percentage is calculated with respect to the height of the generated box's containing block. If the height of the containing block is not specified explicitly (i.e., it depends on content height), and this element is not absolutely positioned, the value computes to 'auto'. ref

    Chrome is doing this with the second image. It consider the height to be auto which is not completely wrong.

    In the CSS3 specification we have another section that allow the browser to do more effort:

    Sometimes the size of a percentage-sized box’s containing block depends on the intrinsic size contribution of the box itself, creating a cyclic dependency. When calculating the intrinsic size contribution of such a box (including any calculations for a content-based automatic minimum size), a cyclic percentage—that is, a percentage value that would resolve against a containing block size which itself depends on that percentage—is resolved specially: ref

    Then you have a set of complex rules and the two importants parts are:

    Otherwise, the percentage is resolved against the containing block’s size. (The containing block’s size is not re-resolved based on the resulting size of the box; the contents might thus overflow or underflow the containing block).

    And one rule with the note:

    Note: Grid items and flex items do allow percentages to resolve in this case.


    To make it easy, the browser will first ignore the percentage height to calculate the height of each grid track based on the content to have the following:

    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      background: red;
      gap: 10px;
    }
    
    img {
      width: 100%;
      /*height: 100%;*/
      object-fit: cover;
    }
    <div class="container">
      <div class="item">
        <img src="https://unsplash.it/450/450">
      </div>
      <div class="item">
        <img src="https://unsplash.it/450/450">
        text
      </div>
    </div>

    Then considering the previous calculated height of grid track we resolve the percentage height to get the following and we don't get back to recalculate the height of the track again:

    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      background: red;
      gap: 10px;
    }
    
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    <div class="container">
      <div class="item">
        <img src="https://unsplash.it/450/450">
      </div>
      <div class="item">
        <img src="https://unsplash.it/450/450">
        text
      </div>
    </div>

    The text will logically overflow since the height was defined by the image+text and we told to the image to take all the height making the text outside and making the image filling all the space.

    Using any percentage value will give the same result which is make the image X% of the height defined by the image+text.


    Setting an explicit height will make both behave the same since we have no complex calculation and we can rely on the CSS2 part of the specification:

    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      grid-template-rows:200px;
      background: red;
      gap: 10px;
    }
    
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    <div class="container">
      <div class="item">
        <img src="https://unsplash.it/450/450">
      </div>
      <div class="item">
        <img src="https://unsplash.it/450/450">
        text
      </div>
    </div>

    Adding an extra wrapper will make the height fail to auto in all the cases as it become more complex1 for browsers to handle such case.

    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      background: red;
      gap: 10px;
    }
    
    img {
      width: 100%;
      height: 958%;
      object-fit: cover;
    }
    <div class="container">
      <div class="item">
        <div>
          <img src="https://unsplash.it/450/450">
        </div>
      </div>
      <div class="item">
        <div>
          <img src="https://unsplash.it/450/450">
        </div>
        text
      </div>
    </div>

    Adding height:100% to the extra wrapper will put as back to the previous cases and height can be resolved again:

    .container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      background: red;
      gap: 10px;
    }
    
    img {
      width: 100%;
      height: 50%;
      object-fit: cover;
    }
    
    .item>div {
      height: 100%;
    }
    <div class="container">
      <div class="item">
        <div>
          <img src="https://unsplash.it/450/450">
        </div>
      </div>
      <div class="item">
        <div>
          <img src="https://unsplash.it/450/450">
        </div>
        text
      </div>
    </div>

    In this case, the div is taking the height defined by the image+text (the text will overflow) then the image inside the div will take half that height.


    Related questions with similar situations:

    https://stackoverflow.com/a/52137966/8620333

    Chrome / Firefox percentage height differences in CSS Grid

    Grid gap percentage without height

    1: I won't be able to give the exact part explaining why we cannot resolve in this case.