Search code examples
cssfirefoxflexboxcenteringcss-grid

Firefox: Item inside flexbox container does not preserve aspect ratio


I have bumped into this, while solving another issue.
I have the following, basic, layout:

+-----+-----------------+
|     |                 |
|  c  |c   +------+     |
|  o  |o   | item |     |
|  l  |l   |      |     |
|     |    +------+     |
|  1  |2                |
+-----------------------+

col 1 and col 2 are created via CSS Grid. Now, I am striving to center item (both horizontally and vertically) inside the col 2.

#content {
  display: grid;
  grid-template-columns: minmax(13rem, 15%) minmax(85%, 100%);
  
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
}

#circle {
  background: #7FFF00;
  
  width: 9%;
  padding-top: 9%;
  border-radius: 50%;
  
  margin: auto;
}

.menu {
  background: #D2691E;
  
  grid-row-start: 1;
  grid-row-end: 1;
}

.circle-area {
  background: #191970;
  
  grid-row-start: 1;
  grid-row-end: 1;
  grid-column-start: auto;
  grid-column-end: span 2;
  
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
<body>
<div id='content'>
<div class='menu'></div>
<div class='circle-area'>
<div id='circle'></div>
</div>
</div>
</body>

Code above on JSFiddle

My code is working as I expect it to (item saves it's aspect ratio and being centered vertically and horizontally), when tested in Chrome 62.0 and Safari 11.0. Though when I've got to Firefox (56.0) - the aspect ratio is changed on window resizing. I have tried another centering technique - using grid (JSFiddle) (the result is the same Firefox won't preserve aspect ratio).

Now, commenting out (JSFiddle) the flex part from css, will make Firefox preserve aspect ratio, though item is not centered vertically anymore.

My questions are:

  1. Is this a known issue (bug) in Firefox, or something wrong with my code?
  2. What is the proper fix/workaround to center item vertically and horizontally, inside col 2, while preserving aspect ratio (preferably using flex)?

Solution

  • Browser behavior on percentage-based vertical margins/paddings in a nutshell

    It is not a bug per se, but probably due to browser's different implementation how percentage-based vertical spacings (top and bottom margins/paddings) should be calculated in flex or grid layout contexts:

    Percentage margins and paddings on grid items can be resolved against either:

    • their own axis (left/right percentages resolve against width, top/bottom resolve against height), or,
    • the inline axis (left/right/top/bottom percentages all resolve against width)

    A User Agent must choose one of these two behaviors.

    There is no consistency of choosing either resolution strategy: as you can see, Chrome and Safari will choose strategy #2, while IE, Edge, and Firefox will go for strategy #1 (which explains your bug). W3C also noted that:

    Note: This variance sucks, but it accurately captures the current state of the world (no consensus among implementations, and no consensus within the CSSWG). It is the CSSWG’s intention that browsers will converge on one of the behaviors, at which time the spec will be amended to require that.

    Authors should avoid using percentages in paddings or margins on grid items entirely, as they will get different behavior in different browsers.


    The solution

    What you can do is simply to define the circle itself as a pseudo-element. In this approach:

    • the outer #circle element has a width of 9%, but nothing else
    • the ::before pseudo-element will have a width of 100% and a padding-top of 100%, this will force it to a 1:1 aspect ratio as desired

    Your updated CSS will look like this:

    #circle {
      width: 9%;
      margin: auto;
    }
    
    #circle::before {
      background: #7FFF00;
      border-radius: 50%;
      width: 100%;
      height: 0;
      padding-top: 100%;
      content: '';
      display: block;
    }
    

    See proof-of-concept below (or in this fiddle):

    #content {
      display: grid;
      grid-template-columns: minmax(13rem, 15%) minmax(85%, 100%);
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      width: 100%;
      height: 100%;
    }
    
    #circle {
      width: 9%;
      margin: auto;
    }
    
    #circle::before {
      background: #7FFF00;
      border-radius: 50%;
      width: 100%;
      height: 0;
      padding-top: 100%;
      content: '';
      display: block;
    }
    
    .menu {
      background: #D2691E;
      grid-row-start: 1;
      grid-row-end: 1;
    }
    
    .circle-area {
      background: #191970;
      grid-row-start: 1;
      grid-row-end: 1;
      grid-column-start: auto;
      grid-column-end: span 2;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 100%;
      height: 100%;
    }
    <body>
      <div id='content'>
        <div class='menu'></div>
        <div class='circle-area'>
          <div id='circle'></div>
        </div>
      </div>
    </body>