how to handle images when flex direction is set to column - html

I am working with flex layouts and images. My goal is to stack the flex-items on top of each other on smaller screen widths, by setting flex-direction: column; but there is a strange behavior i can't understand. The flex item that wraps the image seems to take the full width of the image. I have created a CodePen that illustrates the problem. Thank you in advance

Let the width of the flex item be dictated by the flex container, then size the image to the size of the flex item.
When you set up a flex-direction: column flex container, the height of your flex items is controlled by the flex properties (grow, shrink, basis).
The width of a flex item in a flex-direction: column flex container can either be dictated by the flex container, or it can be left to the flex item to “decide” how big it wants to be.
In this case align-items: start controls the width of your flex items. start means that the items can size themselves however they want, and they will be positioned at the start side of your flex container.
The flex item that wraps your image doesn’t have its own size (it has the default width: auto), so your image’s width: 100% can’t be resolved. In that case the image behaves as though it had width: auto, and basically renders at its full size. Then, the flex item takes on that size, and that’s why it’s so big in your flex container.
You can force all your flex items to be the width of your column flex container by not setting align-items and letting it use the default normal value, with stretches the flex items to the width of the flex container. Then your flex items have a specific size, and your image’s width: 100% can be resolved against that size.
Here’s a working example:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
background-color: #ccc;
padding: 10px;
display: flex;
flex-direction: column;
}
.item {
background-color: #ff1b68;
padding: 40px;
margin: 3px;
color: #fff;
font-size: 40px;
}
.item--2 {
height: 200px;
}
.item--3 {
}
.item--3 img {
width: 100%;
height: 100%;
object-fit: cover;
}
<div class="container">
<div class="item item--1">1</div>
<div class="item item--2">2</div>
<div class="item item--3">
<img
src="https://images7.alphacoders.com/462/thumb-1920-462576.jpg"
alt=""
/>
</div>
</div>
By default, each flex item has align-self: auto, which means it will look at the flex container’s align-items value. So you set align-items once, and all flex items use that. But you can specify different align-self for each item, if you want to, for example, keep your text flex items align-self: start, and your image flex item align-self: stretch.

You can use a calculated width on your image element setting it to view width units => width: calc(100vw - padding - margin). So your image element will be 100% of the view width minus the padding and margin of the it and its parent element. Also set your container to min-width: 1000px;.
If the following is not what you are looking for, please let me know.
Resize the browser to test dynamic resizing of image to fit within viewable screen
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
background-color: #ccc;
padding: 10px;
min-height: 1000px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.item {
background-color: #ff1b68;
padding: 40px;
margin: 3px;
color: #fff;
font-size: 40px;
/*flex-grow: 1;*/
}
.item--2 {
height: 200px;
}
.item--3 {
order: 1;
}
.item--3 img {
width: calc(100vw - 130px);
object-fit: cover;
}
<div class="container">
<div class="item item--1">1</div>
<div class="item item--2">2</div>
<div class="item item--3">
<img src="https://images7.alphacoders.com/462/thumb-1920-462576.jpg" alt="">
</div>
</div>

Related

Why flex item with flex-basis: auto doesn't get all available space if its content's width is 100%?

.parent {
display: flex;
border: 1px solid black;
}
.first>input {
display: block;
width: 100%;
padding: 0;
}
<div class="parent">
<div class="first"><input type="text"></div>
<div class="second"><button>Button</button></div>
</div>
In this sample I'm doing something with the input's styles that will shrink its width as the .parent's width becomes smaller itself. However, it puzzles me why, as long as the .parent's width is more than enough, the .first>input brotherhood don't take up all the available space? There are no max-width set on them, so why should they freeze up in a flex container? What's the rules here?
flex-basis: auto looks up the main size of the element and defines the size. For example, on a horizontal flex container, auto will look for width and height if the container axis is vertical. If no size is specified, auto will fall back to content.
~ Flex Basis Property in Flexbox
So in your case, no size was specified on the flex container. Set the flex-basis on the parent of the element you are trying to grow. In your case, it would be .first.
.parent {
display: flex;
align-items: center;
border: 1px solid black;
}
.first>input {
display: block;
width: 100%;
padding: 0;
}
.first {
flex-basis: 100%;
}
<div class="parent">
<div class="first"><input type="text"></div>
<div class="second"><button>Button</button></div>
</div>

Height of relative div is 100% until I actually set height: 100%

Setup
.container {
position: relative;
display: flex;
flex-direction: row;
justify-content: flex-start;
flex: 1 1;
padding: 28px;
width: 100%;
background-color: #eee;
}
.bar {
position: relative;
width: 5px;
background-color: blue;
}
<div class="container">
<div class="bar"></div>
<div>
lskdjf
</div>
</div>
Notice that the blue bar reaches the full height of the container, minus the padding.
If I add height: 100% to the .bar class however, the height disappears.
.bar {
position: relative;
width: 5px;
background-color: blue;
height: 100%;
}
Question
I imagine that actually setting height to 100% confuses the browser because the parent doesn't actually have a height that is set, but what property pre-setting-height-to-100% allows the height to then be 100%? And, given that this is actually my goal, would it be "correct" to just not specify 100%, or is there a better way to ensure the .bar element reaches the full height?
This is due to the stretch default alignment applied to flexbox container that make all the element stretched to fit their parent height.
.container {
position: relative;
display: flex;
flex-direction: row;
justify-content: flex-start;
flex: 1 1;
padding: 28px;
width: 100%;
background-color: #eee;
}
.bar {
position: relative;
width: 5px;
background-color: blue;
}
<div class="container">
<div class="bar"></div>
<div>
lskdjf
</div>
</div>
If the cross size property of the flex item computes to auto, and neither of the cross-axis margins are auto, the flex item is stretched. Its used value is the length necessary to make the cross size of the item’s margin box as close to the same size as the line as possible, while still respecting the constraints imposed by min-height/min-width/max-height/max-width. ref
If you change the alignment this will no more happen
.container {
position: relative;
display: flex;
flex-direction: row;
justify-content: flex-start;
flex: 1 1;
padding: 28px;
width: 100%;
background-color: #eee;
align-items:flex-start;
}
.bar {
position: relative;
width: 5px;
background-color: blue;
}
<div class="container">
<div class="bar"></div>
<div>
lskdjf
</div>
</div>
And if you set any value of height, the size will no more be auto considering the above specification so the stretch will no more apply and you will fall into the issue of percentage height that will make the height fall to auto because the parent height is not explicitely set.
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

How do I set the height of a flex item to 100% the height of its parent? [duplicate]

I want to have a vertical menu with a specific height.
Each child must fill the height of the parent and have middle-aligned text.
The number of children is random, so I have to work with dynamic values.
Div .container contains a random number of children (.item) that always have to fill the height of the parent. To achieve that I used flexbox.
For making links with text aligned to the middle I am using display: table-cell technique. But using table displays requires using a height 100%.
My problem is that .item-inner { height: 100% } is not working in webkit (Chrome).
Is there a fix for this problem?
Or is there a different technique to make all .item fill the height of the parent with text vertical aligned to middle?
Example here jsFiddle, should be viewed in Firefox and Chrome
.container {
height: 20em;
display: flex;
flex-direction: column;
border: 5px solid black
}
.item {
flex: 1;
border-bottom: 1px solid white;
}
.item-inner {
height: 100%;
width: 100%;
display: table;
}
a {
background: orange;
display: table-cell;
vertical-align: middle;
}
<div class="container">
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
</div>
Solution
Use nested flex containers.
Get rid of percentage heights. Get rid of table properties. Get rid of vertical-align. Avoid absolute positioning. Just stick with flexbox all the way through.
Apply display: flex to the flex item (.item), making it a flex container. This automatically sets align-items: stretch, which tells the child (.item-inner) to expand the full height of the parent.
Important: Remove specified heights from flex items for this method to work. If a child has a height specified (e.g. height: 100%), then it will ignore the align-items: stretch coming from the parent. For the stretch default to work, the child's height must compute to auto (full explanation).
Try this (no changes to HTML):
.container {
display: flex;
flex-direction: column;
height: 20em;
border: 5px solid black
}
.item {
display: flex; /* new; nested flex container */
flex: 1;
border-bottom: 1px solid white;
}
.item-inner {
display: flex; /* new; nested flex container */
flex: 1; /* new */
/* height: 100%; <-- remove; unnecessary */
/* width: 100%; <-- remove; unnecessary */
/* display: table; <-- remove; unnecessary */
}
a {
display: flex; /* new; nested flex container */
flex: 1; /* new */
align-items: center; /* new; vertically center text */
background: orange;
/* display: table-cell; <-- remove; unnecessary */
/* vertical-align: middle; <-- remove; unnecessary */
}
<div class="container">
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
</div>
jsFiddle demo
Explanation
My problem is that .item-inner { height: 100% } is not working in
webkit (Chrome).
It's not working because you're using percentage height in a way that doesn't conform with the traditional implementation of the spec.
10.5 Content height: the height property
percentage 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 and this element is not absolutely positioned, the value computes to auto.
auto The height depends on the values of other properties.
In other words, for percentage height to work on an in-flow child, the parent must have a set height.
In your code, the top-level container has a defined height: .container { height: 20em; }
The third-level container has a defined height: .item-inner { height: 100%; }
But between them, the second-level container – .item – does not have a defined height. Webkit sees that as a missing link.
.item-inner is telling Chrome: give me height: 100%. Chrome looks to the parent (.item) for reference and responds: 100% of what? I don't see anything (ignoring the flex: 1 rule that is there). As a result, it applies height: auto (content height), in accordance with the spec.
Firefox, on the other hand, now accepts a parent's flex height as a reference for the child's percentage height. IE11 and Edge accept flex heights, as well.
Also, Chrome will accept flex-grow as an adequate parent reference if used in conjunction with flex-basis (any numerical value works (auto won't), including flex-basis: 0). As of this writing, however, this solution fails in Safari.
#outer {
display: flex;
flex-direction: column;
height: 300px;
background-color: white;
border: 1px solid red;
}
#middle {
flex-grow: 1;
flex-basis: 1px;
background-color: yellow;
}
#inner {
height: 100%;
background-color: lightgreen;
}
<div id="outer">
<div id="middle">
<div id="inner">
INNER
</div>
</div>
</div>
Four Solutions
1. Specify a height on all parent elements
A reliable cross-browser solution is to specify a height on all parent elements. This prevents missing links, which Webkit-based browsers consider a violation of the spec.
Note that min-height and max-height are not acceptable. It must be the height property.
More details here: Working with the CSS height property and percentage values
2. CSS Relative & Absolute Positioning
Apply position: relative to the parent and position: absolute to the child.
Size the child with height: 100% and width: 100%, or use the offset properties: top: 0, right: 0, bottom: 0, left: 0.
With absolute positioning, percentage height works without a specified height on the parent.
3. Remove unnecessary HTML containers (recommended)
Is there a need for two containers around button? Why not remove .item or .item-inner, or both? Although button elements sometimes fail as flex containers, they can be flex items. Consider making button a child of .container or .item, and removing gratuitous mark-up.
Here's an example:
.container {
height: 20em;
display: flex;
flex-direction: column;
border: 5px solid black
}
a {
flex: 1;
background: orange;
border-bottom: 1px solid white;
display: flex; /* nested flex container (for aligning text) */
align-items: center; /* center text vertically */
justify-content: center; /* center text horizontally */
}
<div class="container">
<a>Button</a>
<a>Button</a>
<a>Button</a>
</div>
4. Nested Flex Containers (recommended)
Get rid of percentage heights. Get rid of table properties. Get rid of vertical-align. Avoid absolute positioning. Just stick with flexbox all the way through.
Apply display: flex to the flex item (.item), making it a flex container. This automatically sets align-items: stretch, which tells the child (.item-inner) to expand the full height of the parent.
Important: Remove specified heights from flex items for this method to work. If a child has a height specified (e.g. height: 100%), then it will ignore the align-items: stretch coming from the parent. For the stretch default to work, the child's height must compute to auto (full explanation).
Try this (no changes to HTML):
.container {
display: flex;
flex-direction: column;
height: 20em;
border: 5px solid black
}
.item {
display: flex; /* new; nested flex container */
flex: 1;
border-bottom: 1px solid white;
}
.item-inner {
display: flex; /* new; nested flex container */
flex: 1; /* new */
/* height: 100%; <-- remove; unnecessary */
/* width: 100%; <-- remove; unnecessary */
/* display: table; <-- remove; unnecessary */
}
a {
display: flex; /* new; nested flex container */
flex: 1; /* new */
align-items: center; /* new; vertically center text */
background: orange;
/* display: table-cell; <-- remove; unnecessary */
/* vertical-align: middle; <-- remove; unnecessary */
}
<div class="container">
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
</div>
jsFiddle
Specifying a flex attribute to the container worked for me:
.container {
flex: 0 0 auto;
}
This ensures the height is set and doesn't grow either.
Solution: Remove height: 100% in .item-inner and add display: flex in .item
Demo: https://codepen.io/tronghiep92/pen/NvzVoo
For Mobile Safari There is a Browser fix. you need to add -webkit-box for iOS devices.
Ex.
display: flex;
display: -webkit-box;
flex-direction: column;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
align-items: stretch;
if you're using align-items: stretch; property for parent element, remove the height : 100% from the child element.
I have had a similar issue in iOS 8, 9 and 10 and the info above couldn't fix it, however I did discover a solution after a day of working on this. Granted it won't work for everyone but in my case my items were stacked in a column and had 0 height when it should have been content height. Switching the css to be row and wrap fixed the issue. This only works if you have a single item and they are stacked but since it took me a day to find this out I thought I should share my fix!
.wrapper {
flex-direction: column; // <-- Remove this line
flex-direction: row; // <-- replace it with
flex-wrap: wrap; // <-- Add wrapping
}
.item {
width: 100%;
}

How can I have 100% height nested flex containers in Chrome? [duplicate]

I want to have a vertical menu with a specific height.
Each child must fill the height of the parent and have middle-aligned text.
The number of children is random, so I have to work with dynamic values.
Div .container contains a random number of children (.item) that always have to fill the height of the parent. To achieve that I used flexbox.
For making links with text aligned to the middle I am using display: table-cell technique. But using table displays requires using a height 100%.
My problem is that .item-inner { height: 100% } is not working in webkit (Chrome).
Is there a fix for this problem?
Or is there a different technique to make all .item fill the height of the parent with text vertical aligned to middle?
Example here jsFiddle, should be viewed in Firefox and Chrome
.container {
height: 20em;
display: flex;
flex-direction: column;
border: 5px solid black
}
.item {
flex: 1;
border-bottom: 1px solid white;
}
.item-inner {
height: 100%;
width: 100%;
display: table;
}
a {
background: orange;
display: table-cell;
vertical-align: middle;
}
<div class="container">
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
</div>
Solution
Use nested flex containers.
Get rid of percentage heights. Get rid of table properties. Get rid of vertical-align. Avoid absolute positioning. Just stick with flexbox all the way through.
Apply display: flex to the flex item (.item), making it a flex container. This automatically sets align-items: stretch, which tells the child (.item-inner) to expand the full height of the parent.
Important: Remove specified heights from flex items for this method to work. If a child has a height specified (e.g. height: 100%), then it will ignore the align-items: stretch coming from the parent. For the stretch default to work, the child's height must compute to auto (full explanation).
Try this (no changes to HTML):
.container {
display: flex;
flex-direction: column;
height: 20em;
border: 5px solid black
}
.item {
display: flex; /* new; nested flex container */
flex: 1;
border-bottom: 1px solid white;
}
.item-inner {
display: flex; /* new; nested flex container */
flex: 1; /* new */
/* height: 100%; <-- remove; unnecessary */
/* width: 100%; <-- remove; unnecessary */
/* display: table; <-- remove; unnecessary */
}
a {
display: flex; /* new; nested flex container */
flex: 1; /* new */
align-items: center; /* new; vertically center text */
background: orange;
/* display: table-cell; <-- remove; unnecessary */
/* vertical-align: middle; <-- remove; unnecessary */
}
<div class="container">
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
</div>
jsFiddle demo
Explanation
My problem is that .item-inner { height: 100% } is not working in
webkit (Chrome).
It's not working because you're using percentage height in a way that doesn't conform with the traditional implementation of the spec.
10.5 Content height: the height property
percentage 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 and this element is not absolutely positioned, the value computes to auto.
auto The height depends on the values of other properties.
In other words, for percentage height to work on an in-flow child, the parent must have a set height.
In your code, the top-level container has a defined height: .container { height: 20em; }
The third-level container has a defined height: .item-inner { height: 100%; }
But between them, the second-level container – .item – does not have a defined height. Webkit sees that as a missing link.
.item-inner is telling Chrome: give me height: 100%. Chrome looks to the parent (.item) for reference and responds: 100% of what? I don't see anything (ignoring the flex: 1 rule that is there). As a result, it applies height: auto (content height), in accordance with the spec.
Firefox, on the other hand, now accepts a parent's flex height as a reference for the child's percentage height. IE11 and Edge accept flex heights, as well.
Also, Chrome will accept flex-grow as an adequate parent reference if used in conjunction with flex-basis (any numerical value works (auto won't), including flex-basis: 0). As of this writing, however, this solution fails in Safari.
#outer {
display: flex;
flex-direction: column;
height: 300px;
background-color: white;
border: 1px solid red;
}
#middle {
flex-grow: 1;
flex-basis: 1px;
background-color: yellow;
}
#inner {
height: 100%;
background-color: lightgreen;
}
<div id="outer">
<div id="middle">
<div id="inner">
INNER
</div>
</div>
</div>
Four Solutions
1. Specify a height on all parent elements
A reliable cross-browser solution is to specify a height on all parent elements. This prevents missing links, which Webkit-based browsers consider a violation of the spec.
Note that min-height and max-height are not acceptable. It must be the height property.
More details here: Working with the CSS height property and percentage values
2. CSS Relative & Absolute Positioning
Apply position: relative to the parent and position: absolute to the child.
Size the child with height: 100% and width: 100%, or use the offset properties: top: 0, right: 0, bottom: 0, left: 0.
With absolute positioning, percentage height works without a specified height on the parent.
3. Remove unnecessary HTML containers (recommended)
Is there a need for two containers around button? Why not remove .item or .item-inner, or both? Although button elements sometimes fail as flex containers, they can be flex items. Consider making button a child of .container or .item, and removing gratuitous mark-up.
Here's an example:
.container {
height: 20em;
display: flex;
flex-direction: column;
border: 5px solid black
}
a {
flex: 1;
background: orange;
border-bottom: 1px solid white;
display: flex; /* nested flex container (for aligning text) */
align-items: center; /* center text vertically */
justify-content: center; /* center text horizontally */
}
<div class="container">
<a>Button</a>
<a>Button</a>
<a>Button</a>
</div>
4. Nested Flex Containers (recommended)
Get rid of percentage heights. Get rid of table properties. Get rid of vertical-align. Avoid absolute positioning. Just stick with flexbox all the way through.
Apply display: flex to the flex item (.item), making it a flex container. This automatically sets align-items: stretch, which tells the child (.item-inner) to expand the full height of the parent.
Important: Remove specified heights from flex items for this method to work. If a child has a height specified (e.g. height: 100%), then it will ignore the align-items: stretch coming from the parent. For the stretch default to work, the child's height must compute to auto (full explanation).
Try this (no changes to HTML):
.container {
display: flex;
flex-direction: column;
height: 20em;
border: 5px solid black
}
.item {
display: flex; /* new; nested flex container */
flex: 1;
border-bottom: 1px solid white;
}
.item-inner {
display: flex; /* new; nested flex container */
flex: 1; /* new */
/* height: 100%; <-- remove; unnecessary */
/* width: 100%; <-- remove; unnecessary */
/* display: table; <-- remove; unnecessary */
}
a {
display: flex; /* new; nested flex container */
flex: 1; /* new */
align-items: center; /* new; vertically center text */
background: orange;
/* display: table-cell; <-- remove; unnecessary */
/* vertical-align: middle; <-- remove; unnecessary */
}
<div class="container">
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
</div>
jsFiddle
Specifying a flex attribute to the container worked for me:
.container {
flex: 0 0 auto;
}
This ensures the height is set and doesn't grow either.
Solution: Remove height: 100% in .item-inner and add display: flex in .item
Demo: https://codepen.io/tronghiep92/pen/NvzVoo
For Mobile Safari There is a Browser fix. you need to add -webkit-box for iOS devices.
Ex.
display: flex;
display: -webkit-box;
flex-direction: column;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
align-items: stretch;
if you're using align-items: stretch; property for parent element, remove the height : 100% from the child element.
I have had a similar issue in iOS 8, 9 and 10 and the info above couldn't fix it, however I did discover a solution after a day of working on this. Granted it won't work for everyone but in my case my items were stacked in a column and had 0 height when it should have been content height. Switching the css to be row and wrap fixed the issue. This only works if you have a single item and they are stacked but since it took me a day to find this out I thought I should share my fix!
.wrapper {
flex-direction: column; // <-- Remove this line
flex-direction: row; // <-- replace it with
flex-wrap: wrap; // <-- Add wrapping
}
.item {
width: 100%;
}

What is the difference between the flex and height properties?

Given the following example, both will fill out the center to consume the remaining space in the page, given the page is using flex. I am leaning towards using the css property flex vs height in the body. Is there a difference that needs to be considered when applying one over the other?
CSS
.page {
display: flex;
flex-direction: column;
}
.header {
height: 100px;
}
.body {
flex: 1; // vs height: 100%;
}
.footer {
height: 40px;
}
HTML
<div class="page">
<div class="header">Sample Header</div>
<div class="body">Sample Body</div>
<div class="footer">Sample Footer</div>
</div>
When you set an element to flex: 1, that breaks down to:
flex-grow: 1
flex-shrink: 1
flex-basis: 0
In a column-direction container (like you have), the flex properties above apply vertically. This means that flex-basis and height are equivalent properties.
flex-basis = height (in a column-direction container)
There is an obvious difference between flex-basis: 0 and height: 100%. It's the same difference as height: 0 and height: 100%.
In your situation, where there is a .header and a .footer consuming 140px of vertical space, setting the middle item (.body) to height: 100% would normally cause an overflow.
But since an initial value of a flex container is flex-shrink: 1, flex items are permitted to shrink, and this wouldn't happen. However, it's still sloppy and imprecise coding, in my view.
By setting .body to flex: 1, you're setting the height to 0, but also allowing it to consume free height with flex-grow: 1. I would say, in this case, that this solution is more efficient.
More details:
What are the differences between flex-basis and width?
§ 7.1.1. Basic Values of flex
There is a huge difference between flex and height.
First to answer your question.
Height 100% doesn't use the remaining space. It will use all the spaces of parent, in your case if page dom is height 200px; then body will also be height: 200px;.
Flex will be correct solution here to fill up the space (flex: 1).
Flex is more than filling the space, its more of a layout and it has influences on its child, how they position and align.
Try below code
.page {
display: flex;
flex-direction: column;
height: 100%;
}
.header {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
.body {
display: flex;
flex-direction: column;
height: 80vh;
align-items: center;
justify-content: center;
}
.footer {
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
<div class="page">
<div class="header">Sample Header</div>
<div class="body">Sample Body</div>
<div class="footer">Sample Footer</div>
</div>