5

I want the graph div to have a height that stretches all the way to the end of it's parent (graph-container) container, but not extend past it. I have tried setting graph's height to both 100% and inherit, but both of them result in it extending past it's parent container's bottom edge (I don't want to use overflow: hidden). Is there a way to set the height property on graph so it automatically sets its height to the right value?

Current output:output

What I want: enter image description here

Current code:

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

.wrapper {
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
}

.graph-container {
  border: 3px solid black;
  height: 60vh;
  width: 70vw;
  margin-top: 10vh;
}

.graph {
  height: 100%;
  width: 100%;
  border: 3px solid red;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="wrapper">
      <div class="graph-container">
        <h1>Graph Container</h1>
        <div class="graph">Graph</div>
      </div>
    </div>
  </body>
</html>

2 Answers2

6

The overflow you see happens because there is already an element taking up some space of the parent, so height: 100% plus the height of the other element will obviously exceed the parents height.

There are different ways to solve the problem.
I recommend one of these two ways:

However, you can also solve them using these ways:

CSS Flexbox

The parent needs display: flex and flex-direction: column to lay out its children in a column.

The child that should grow to take up the remaining space needs flex-grow: 1.

/* Ignore; only for displaying the example */
* {
  margin: 0;
  font-size: 24px;
}
html, body {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.graph-container, .graph {
  border: 3px solid black;
  box-sizing: border-box;
}

/* The answer */
.graph-container {
  height: 60vh;
  width: 70vw;
  
  display: flex;
  flex-flow: column; /* Shorthand for ('flex-direction' | 'flex-wrap') */
}
.graph {
  flex-grow: 1;
  border-color: red;
}
<div class="graph-container">
  <h1>Graph Container</h1>
  <div class="graph">Graph</div>
</div>

CSS Grid

The parent needs display: grid and grid-template-rows: auto 1fr.

grid-template-rows: auto 1fr makes the second child take up the remaining space that is left over after the other children get their respective space that is defined without the fr-unit.
Here, <h1> gets its space it requires, and all the remaining space is given to .graph.

/* Ignore; only for displaying the example */
* {
  margin: 0;
  font-size: 24px;
}
html, body {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.graph-container, .graph {
  border: 3px solid black;
  box-sizing: border-box;
}

/* The answer */
.graph-container {
  height: 60vh;
  width: 70vw;
  
  display: grid;
  grid-template-rows: auto 1fr;
}
.graph {
  border-color: red;
}
<div class="graph-container">
  <h1>Graph Container</h1>
  <div class="graph">Graph</div>
</div>

CSS calc()

Relation of font-size to height

As with the solution of sergey kuznetsov, you can guess that height is (for small sizes only!) equal to font-size.

This is not always true, because height actually depends on line-height, which again depends on the font currently used.

The problem, illustrated

Say for example, that line-height is about 1.05-times taller than font-size for a given font. Since the browser just can't display less than a px-unit, the decimal places are effectively cut off.

That makes the equation only true for small values of font-size, as can be seen in the examples below.
Examples (where toInt() cuts off the decimal places):

  • toInt(20px of 'font-size' * 1.05) = 20px of 'height'
  • toInt(60px of 'font-size' * 1.05) = 63px of 'height'

This means, while this works (for small font-size-values):
calc(100% - 20px - 3px) (20px due to font-size, 3px due to border-width)

/* Ignore; only for displaying the example */
* {
  margin: 0;
  font-size: 24px;
}
html, body {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.graph-container, .graph {
  border: 3px solid black;
  box-sizing: border-box;
}

/* The answer */
.graph-container {
  height: 60vh;
  width: 70vw;
}
h1 {
  font-size: 20px;
}
.graph {
  height: calc(100% - 20px - 3px);
  display: block;
  border-color: red;
}
<div class="graph-container">
  <h1>Title</h1>
  <div class="graph">Graph</div>
</div>

... this won't work (for great font-size-values):
calc(100% - 60px - 3px) (60px due to font-size, 3px due to border-width)

/* Ignore; only for displaying the example */
* {
  margin: 0;
  font-size: 24px;
}
html, body {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.graph-container, .graph {
  border: 3px solid black;
  box-sizing: border-box;
}

/* The answer */
.graph-container {
  height: 60vh;
  width: 70vw;
}
h1 {
  font-size: 60px;
}
.graph {
  height: calc(100% - 60px - 3px);
  display: block;
  border-color: red;
}
<div class="graph-container">
  <h1>Title</h1>
  <div class="graph">Graph</div>
</div>

That is why it is best to actually define the height-property itself instead of relying on font-size or line-height to be the same as height (which it apparently isn't always). Which brings us to...

Using CSS custom properties

You set the height of the second child to the remaining space with calc(100% - TITLE_HEIGHT) where TITLE_HEIGHT is the height of the other child, <h1>.

We can use CSS Custom properties to set the height to reduce the amount of "magic numbers", which makes refactoring the code later on easier, and perhaps already easier to read.
I used --title-height for the height.

Custom properties are only accessible in the element itself and its children, which means that we need to define it in their parent .graph-container.

Now we can use the value of --title-height by resolving it using var() like this:
var(--title-height)

/* Ignore; only for displaying the example */
* {
  margin: 0;
  font-size: 24px;
}
html, body {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.graph-container, .graph {
  border: 3px solid black;
  box-sizing: border-box;
}

/* The answer */
.graph-container {
  --title-height: 26px;
  
  height: 60vh;
  width: 70vw;
  
  display: block;
}
h1 {
  height: var(--title-height);
}
.graph {
  height: calc(100% - var(--title-height));
  display: block;
  border-color: red;
}
<div class="graph-container">
  <h1>Graph Container</h1>
  <div class="graph">Graph</div>
</div>

JavaScript

When using JS, we need to set the parent's height as well as the height of the <h1>-element, and calculate the remaining height for .graph. The remaining height can be set as the .graph's height using Element.style.height.

/* The answer */
var container = document.querySelector('.graph-container');
var title = document.querySelector('h1');
var graph = document.querySelector('.graph');

var remainingHeight = container.clientHeight - title.clientHeight;
graph.style.height = remainingHeight + "px"; // Make sure it is of unit "px"
/* Ignore; only for displaying the example */
* {
  margin: 0;
  font-size: 24px;
}
html, body {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.graph-container, .graph {
  border: 3px solid black;
  box-sizing: border-box;
}

.graph-container {
  height: 60vh;
  width: 70vw;
}
.graph {
  border-color: red;
  display: block;
}
<div class="graph-container">
  <h1>Graph Container</h1>
  <div class="graph">Graph</div>
</div>

The problem with this approach is, that the height will be fixed once found. That will make .graph not responsive. To achieve that .graph is responsive, we need to observe if the parent was resized using a ResizeObserver.

Every time the parent resizes, recalculate the size of .graph.

/* The answer */
var container = document.querySelector('.graph-container');
var title = document.querySelector('h1');
var graph = document.querySelector('.graph');

new ResizeObserver(() => {
  graph.style.height = container.clientHeight - title.clientHeight + "px";
}).observe(container);
/* Ignore; only for displaying the example */
* {
  margin: 0;
  font-size: 24px;
}
html, body {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.graph-container, .graph {
  border: 3px solid black;
  box-sizing: border-box;
}

.graph-container {
  height: 60vh;
  width: 70vw;
}
.graph {
  border-color: red;
  display: block;
}
<div class="graph-container">
  <h1>Graph Container</h1>
  <div class="graph">Graph</div>
</div>
Oskar Grosser
  • 2,804
  • 1
  • 7
  • 18
1

I also want to suggest a solution using calc(). Namely:

height: calc(100% - 23px);

Where 23px is the h1 tag (font-size: 20px) and the border graph class is 3 pixels (border: 3px solid red).

In this example, the height of the child div will fit neatly inside the parent div.

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

.wrapper {
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
}

.graph-container {
  border: 3px solid black;
  height: 60vh;
  width: 70vw;
  margin-top: 10vh;
}

.graph {
  height: calc(100% - 23px);
  width: 100%;
  border: 3px solid red;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="wrapper">
      <div class="graph-container">
        <h1>Graph Container</h1>
        <div class="graph">Graph</div>
      </div>
    </div>
  </body>
</html>
s.kuznetsov
  • 14,870
  • 3
  • 10
  • 25
  • Note that `font-size` isn't equal to `line-height`. For greater `font-size`s, the actual elements height will differ from it. The `line-height` is dependent on the Font used, and is (at least in chrome I think) by default about `1.027`-times larger than the `font-size`. However, The `height` cannot fractions of a pixel tall, so the decimal places are "cut off", making it seem like `20px of 'font-size' = 20px of 'height'` whilst it actually is `floor(20px of 'font-size' * 1.027) = 20px of 'height'` where `floor()` "cuts off" the decimal places. – Oskar Grosser Dec 08 '20 at 09:07
  • @OskarGrosser, yes, I know that. In this case, the `line-height` is not applied, so I gave this solution. – s.kuznetsov Dec 08 '20 at 09:12
  • 1
    Okay then yes, you are absolutely right, here it is a feasible solution to do this. It would be great however to at least mention that this might become a problem for greater font-sizes. – Oskar Grosser Dec 08 '20 at 09:14
  • @OskarGrosser, thanks, but your solutions are the best! :) – s.kuznetsov Dec 08 '20 at 09:16
  • I added a sub-section regarding our "discussion" in my answer. You can edit it in case you have something to add. – Oskar Grosser Dec 08 '20 at 10:45