4

I've got a delicate problem for any CSS guru out there. My green div has a flexible height, taking up the remaining. And now I want to put a div inside that div which should be the half of the green div. But it seems like if Chrome treats it like half of the whole page rather than the flex item.

http://jsfiddle.net/unh5rw9t/1/

HTML

<body>
    <div id="wrapper">
        <div id="menu">
            1
        </div>
        <div id="content">2
            <div id="half_of_content">2.1</div>
        </div>
        <div id="footer" style="">
            3
        </div>
    </div>
</body>

CSS

html,body {
    height: 100%;
    margin: 0;
}

#wrapper {
    display: flex;
  flex-flow: column;
    height: 100%;
}

#menu {
    height: 70px; 
    background-color: purple
}

#content {
    flex: 1;
    height: 100%;
    background-color: green;
}

#half_of_content {
    height: 50%;
    background-color: yellow;
}

#footer {
    height: 100px; 
    background-color: cyan
}
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701

4 Answers4

3

@Michael_B explained why Chrome behaves like this:

You gave the body a height: 100%. Then gave its child (.wrapper) a height: 100%. Then gave its child (.content) a height: 100%. So they're all equal height. Giving the next child (#half_of_content) a height: 50% would naturally be a 50% height of body.

However, Firefox disagrees because, in fact, that height: 100% of .content is ignored and its height is calculated according to flex: 1.

That is, Chrome resolves the percentage with respect to the value of parent's height property. Firefox does it with respect to the resolved flexible height of the parent.

The right behavior is the Firefox's one. According to Definite and Indefinite Sizes,

If a percentage is going to be resolved against a flex item’s main size, and the flex item has a definite flex basis, and the flex container has a definite main size, the flex item’s main size must be treated as definite for the purpose of resolving the percentage, and the percentage must resolve against the flexed main size of the flex item (that is, after the layout algorithm below has been completed for the flex item’s flex container, and the flex item has acquired its final size).

Here is a workaround for Chrome:

#content {
  display: flex;
  flex-direction: column;
}
#content::after {
  content: '';
  flex: 1;
}
#half_of_content {
  flex: 1;
  height: auto;
}

This way the available space in #content will be distributed equally among #half_of_content and the ::after pseudo-element.

Assuming #content doesn't have other content, #half_of_content will be 50%. In your example you have a 2 in there, so it will be a bit less that 50%.

html,
body {
  height: 100%;
  margin: 0;
}
#wrapper {
  display: flex;
  flex-flow: column;
  height: 100%;
}
#menu {
  height: 70px;
  background-color: purple
}
#content {
  flex: 1;
  height: 100%;
  background-color: green;
  display: flex;
  flex-direction: column;
}
#content::after {
  content: '';
  flex: 1;
}
#half_of_content {
  flex: 1;
  background-color: yellow;
}
#footer {
  height: 100px;
  background-color: cyan
}
<div id="wrapper">
  <div id="menu">
    1
  </div>
  <div id="content">2
    <div id="half_of_content">2.1</div>
  </div>
  <div id="footer" style="">
    3
  </div>
</div>
Community
  • 1
  • 1
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • Will I be able to use normal height percentage calculations inside the #half_of_content container without doing the ugly fixes? Because I am going to divide up the space a lot later.. –  Nov 08 '15 at 00:48
  • @Doodlemeat Since `#half_of_content` has `height: auto`, you can't use percentages in the `height` of its children. But you can use the same trick again, e.g. if you want a child to be `70%` set `flex: 70` and add a pseudo-element with `flex: 100`. – Oriol Nov 08 '15 at 00:53
  • That's an interesting distinction between Chrome & FF. I think traditionally, when calculating percentage heights, browsers have interpreted the [spec](http://www.w3.org/TR/CSS21/visudet.html#propdef-height) to mean height as defined by the `height` property. I've never seen `min-height`, `max-height` or `flex-basis` (until now) work on a parent when [dealing with percentage heights](http://stackoverflow.com/a/31728799/3597276). – Michael Benjamin Nov 08 '15 at 03:15
  • The spec, which is a bit old as you point out, seems to require a height (generic term) but not a `height`, although `height` is the predominant implementation. Looks like FF is loosening its interpretation. – Michael Benjamin Nov 08 '15 at 03:15
  • 1
    @Michael_B IMO the Firefox implementation of using the resolved height instead of the value of `height` makes more sense. The other day I found another interesting case in which the height of the container didn't depend directly on the height of the content, but it did through `min-height: auto`. I guess we will have to wait until a new spec clarifies those cases. – Oriol Nov 08 '15 at 17:25
0

You could absolutely position div id="half_of_content".

#content {
    flex: 1;
    height: 100%;
    background-color: green;
    position: relative; /* new */
}

#half_of_content {
    height: 50%;
    background-color: yellow;
    position: absolute; /* new */
    width: 100%; /* new */
}

DEMO

With regard to your statement:

But it seems like if Chrome treats it like half of the whole page rather than the flex item.

You gave the body a height: 100%. Then gave its child (.wrapper) a height: 100%. Then gave its child (.content) a height: 100%. So they're all equal height. Giving the next child (#half_of_content) a height: 50% would naturally be 50% height of body.

With absolute positioning, however, you don't need to specify parent heights.

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • This actually looks better than Oriols solution, although this can seems even more ugly I think. But until Chrome fixed the bug, I will use the easiest method there is. –  Nov 08 '15 at 00:56
  • Well, if I could I would want to avoid setting the height:100% on #wrapper since it already receives a height from flexbox. But then I cant do 50% height on the child element because ... I dont know why.. So we can officially say that **flexitems that grows doesn't receive their actual height** ? –  Nov 08 '15 at 00:58
  • *Ugly??* I see a thing of beauty in that solution :-) The 50% `div` expands and contracts beautifully, gracefully and precisely in the parent `div`. – Michael Benjamin Nov 08 '15 at 01:03
  • Just to be clear, cause I'm not sure which `div` you're talking about, `#half_of_height` in *not* a flex item. – Michael Benjamin Nov 08 '15 at 01:05
  • the 50% means taking half of the parents height. But in this case, it doesn't. –  Nov 08 '15 at 01:08
0

Nesting flexboxes is a little buggy. I reworked your markup a little by adding an inner wrapper with display: flex; which seems to do the job. Here is the fiddle (also using class names instead of ids).

<div class="content">
  <div class="wrapper-inner">
    2
    <div class="half">
      2.1
    </div>
  </div>
</div>

.wrapper-inner {
    position: absolute;
    display: flex;
    flex-direction: column;
    height: 100%;
    width: 100%;
}
Will
  • 19,661
  • 7
  • 47
  • 48
0

Fix:

on #content set

display: flex; 
flex-flow: column nowrap; 
justify-content: flex-end

on #half_of_content set flex: 0 0 50%;

Caveat: you need to add an extra div as a child of #content.

Here's the full example:

html,body {
    height: 100%;
    margin: 0;
}

#wrapper {
    display: flex;
  flex-flow: column;
    height: 100%;
}

#menu {
    height: 70px; 
    background-color: purple
}

#content {
    flex: 1;
    height: 100%;
    display:flex;
    flex-flow: column nowrap;
    justify-content: flex-end;
    background-color: green;
}

#half_of_content {
    flex: 0 0 50%;
    background-color: yellow;
}

#footer {
    height: 100px; 
    background-color: cyan
}
<body>
    <div id="wrapper">
        <div id="menu">
            1
        </div>
        <div id="content">2
            <div id="half_of_content">2.1</div>
        </div>
        <div id="footer" style="">
            3
        </div>
    </div>
</body>
Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100