I have a pretty simple page setup in the following manner using flexboxes:
The blue div is supposed to make up 25% in height and the violet div 75%. In case there are too many lines in the blue div, it should stay the same size an show a scrollbar. This works for a few lines, but breaks at some point and the blue div overflows and grows into the violet one. I'm new to flexboxes, so I don't really understand why this is happening. Would I be better off not using flexboxes? Thankful for any hints or pointer at this point.
This is the code I use (run in full page):
function lines(noLines) {
var text = "line</br>".repeat(noLines);
document.getElementById("lower").innerHTML = text;
}
* {
box-sizing: border-box;
}
.body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
#static1 {
height: 30px;
width: 100%;
background-color: red;
}
#static2 {
height: 40px;
width: 100%;
background-color: orange;
}
#content {
display: flex;
flex: 1;
}
#left {
width: 40%;
background-color: yellow;
}
#right {
width: 60%;
display: flex;
flex-direction: column;
}
#upper {
flex: 3 0;
background-color: violet;
}
#lower {
flex: 1;
background-color: blue;
overflow: auto;
}
<div class="body">
<div id="static1">Some static div</div>
<div id="static2">Another static div. Flexbox below fills rest of remaining screen.</div>
<div id="content">
<div id="left">
Left part, fixed width in percentage.</br>
Click to enter lines into the bottom right:</br>
<button onclick=lines(20)>Few Lines</button>
<button onclick=lines(200)>Many Lines</button>
</div>
<div id="right">
<div id="upper">Flexbox with flex=3.</div>
<div id="lower">Flexbox with flex=1.</div>
</div>
</div>
</div>
For the overflow property to work properly, the container needs an actual height or max-height. Flex heights (you have flex: 1 on .content) won't cut it.
In order for overflow to have an effect, the block-level container
must have either a set height (height or max-height) or
white-space set to nowrap. ~ MDN
Since you already know the height of the primary container (100vh) and the first two rows (30px and 40px), the rest is simple using the calc() function.
function lines(noLines) {
var text = "line</br>".repeat(noLines);
document.getElementById("lower").innerHTML = text;
}
.body {
display: flex;
flex-direction: column;
height: 100vh; /* adjustment */
}
#static1 {
flex-shrink: 0; /* disable shrinking */
height: 30px;
/* width: 100%; */
background-color: red;
}
#static2 {
flex-shrink: 0; /* disable shrinking */
height: 40px;
/* width: 100%; */
background-color: orange;
}
#content {
height: calc(100vh - 70px); /* new */
display: flex;
/* flex: 1; */ /* may work in some browsers, but not reliable */
}
#left {
width: 40%;
background-color: yellow;
}
#right {
width: 60%;
display: flex;
flex-direction: column;
}
#upper {
flex: 3 0;
background-color: violet;
}
#lower {
flex: 1;
background-color: aqua; /* adjusted for illustration */
overflow: auto;
}
body {
margin: 0; /* new; override browser default */
}
* {
box-sizing: border-box;
}
<div class="body">
<div id="static1">Some static div</div>
<div id="static2">Another static div. Flexbox below fills rest of remaining screen.</div>
<div id="content">
<div id="left">
Left part, fixed width in percentage.<br> Click to enter lines into the bottom right:<br>
<button onclick=lines(20)>Few Lines</button>
<button onclick=lines(200)>Many Lines</button>
</div>
<div id="right">
<div id="upper">Flexbox with flex=3.</div>
<div id="lower">Flexbox with flex=1.</div>
</div>
</div>
</div>
jsFiddle demo
I hope this is what you mean, but If I'm wrong, apologies. The problem I can see lies in the way you are using flex: 1 & flex: 3 to define the proportions of the right column, without specifying to what height their parent container has, i.e. #right has no height, so the box can always expand as it gets more filled with content.
Please try this, I hope this works and if I can answer anything else, just ask please.
The only thing I changed was your CSS and added max-height: calc(100vh - 70px); to the #right div. And changed overflow: auto; to overflow-y: scroll;
* {
box-sizing: border-box;
}
.body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
#static1 {
height: 30px;
width: 100%;
background-color: red;
}
#static2 {
height: 40px;
width: 100%;
background-color: orange;
}
#content {
display: flex;
flex: 1;
}
#left {
width: 40%;
background-color: yellow;
}
#right {
width: 60%;
display: flex;
flex-direction: column;
max-height: calc(100vh - 70px);
}
#upper {
flex: 3;
height: 75%;
background-color: violet;
}
#lower {
flex: 1;
height: 25%;
background-color: blue;
overflow-y: scroll;
}
Change the top part of CSS to this:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
Related
I'm trying to make a container with side-by-side divs, one item has a fixed width and the other item has a fixed height. However, the flexible-height div won't shrink below its contents.
.parent {
background: #f00;
display: contents;
}
.container {
background: #0f0;
width: 25vw;
display: flex;
}
.object {
background: #00f;
margin: 10px;
width: 100px;
flex-shrink: 0;
}
.object.flexes-main-axis {
flex-grow: 1;
flex-shrink: 1;
}
.object.sets-cross-axis-size {
height: 75px;
}
.object.shrinks-cross-axis-below-content {
overflow-y: auto;
}
.child {
background: #f0f;
height: 100px;
width: 100px;
}
<div class='parent'>
<div class='container'>
<div id="object1" class='object flexes-main-axis sets-cross-axis-size'></div>
<div id="object2" class='object shrinks-cross-axis-below-content'>
<div class="child"></div>
</div>
</div>
</div>
I'm trying to get object2 to shrink and show a vertical scroll bar when I shrink object1 below 100px. The magenta box represents fixed-height content I can’t shrink, while object1 represents the element I want to control the container’s height.
The blue is hardcoded 75px tall, while the magenta is hardcoded 100px.
As the main .container has no height set it will grow to fit the tallest child. A main .container with a fixed height: ..px would still not make .child shrink. That's Flexbox.
But, if you set the .child to height: 100% and you will see it shrink:
* { outline: 1px dashed } /* for debugging */
.parent {
background: #f00;
display: contents;
}
.container {
background: #0f0;
width: 25vw;
display: flex;
}
.object {
background: #00f;
margin: 10px;
width: 100px;
flex-shrink: 0;
}
.object.flexes-main-axis {
flex-grow: 1;
flex-shrink: 1;
}
.object.sets-cross-axis-size {
height: 75px;
}
.object.shrinks-cross-axis-below-content {
overflow-y: auto;
}
.child {
background: #f0f;
height: 100%; /* Changed from 100px */
width: 100px;
}
<div class='parent'>
<div class='container'>
<div id="object1" class='object flexes-main-axis sets-cross-axis-size'>1</div>
<div id="object2" class='object shrinks-cross-axis-below-content'>
<div class="child">2</div>
</div>
</div>
</div>
So, I figured it out. I needed to add an extra wrapper div between object2 and child with height set to 0. Object2 will scroll to accommodate child overflow, but child's height won't count toward object2's content as far as the flexbox is concerned. Now as you change object1's height below child's height, you'll get a scroll bar in object2.
Also in this version, I made object1 responsive to the viewport width, so you can try it out by resizing the browser window.
.container {
display: flex;
background-color: red;
padding: 10px;
width: 50vw;
}
.sizecontroller {
aspect-ratio: 16 / 9;
background-color: blue;
padding: 10px;
flex-grow: 1;
flex-shrink: 1;
align-self: flex-start;
}
.sizeresponsive {
overflow-y: auto;
background-color: green;
padding: 10px;
flex-grow: 0;
flex-shrink: 0;
}
.sizeeraser {
height: 0;
background-color: purple;
padding: 10px;
}
.sizefixed {
width: 25px;
height: 250px;
background-color: yellow;
padding: 10px;
}
<div class="container">
<div class="sizecontroller"></div>
<div class="sizeresponsive">
<div class="sizeeraser">
<div class="sizefixed"></div>
</div>
</div>
</div>
I'm trying to create an element that will hold various images that should be responsive (width AND height). So far using flexbox has been successful except for one thing. Every time I reduce the width of my window, at a certain point, the flex items overflow the parent container and spill out beyond the containers width.
nav {
position: fixed;
top: 0;
left: 0;
height: 80px;
line-height: 80px;
background: black;
color: #fff;
width: 100%;
text-align: center;
}
body, p {
margin: 0;
padding: 0;
width: 100vw;
}
.wrapper {
height: 100vh;
margin: 100px auto;
min-width: 0;
}
.flex {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
max-width: 100%;
min-width: 200px;
flex-wrap: nowrap;
}
img {
max-height: 100%;
max-width: 100%;
}
.txt-rt {
text-align: right;
}
.footer {
background: darkgray;
height: 300px;
text-align: center;
}
<nav>This is a Navbar</nav>
<div class="wrapper">
<div class="flex">
<p>hello</p>
<img src="https://upload.wikimedia.org/wikipedia/commons/e/eb/Ash_Tree_-_geograph.org.uk_-_590710.jpg" alt="">
<p class="txt-rt">world</p>
</div>
</div>
<div class="footer">
<h3 class="footer">Footer content</h3>
</div>
In this CodePen example, each time the window width is <560px or so and the height is at least 600px, the image is no longer responsive in width and the content overflows outside the screen.
All the other functionality looks like it's working as expected, but once I reduce my window width to a certain point the image will not shrink down. This prevents all 3 flex items being viewable in the width of the screen. Is there code I should be adding - not media queries since various sizes of images will be used - to make sure the image is responsive no matter the size of the window? Note: I don't want the items to wrap down to a second line.
You can use this code
* {
box-sizing: border-box;
}
body {
display: flex;
min-height: 100vh;
flex-direction: column;
margin: 0;
text-align: center;
}
#main {
display: flex;
flex: 1;
flex-direction: column;
}
#main>article {
flex: 1;
text-align: center;
}
#main>nav,
#main>aside {
background: beige;
}
#main>nav {
order: -1;
}
header,
footer {
background: yellowgreen;
height: 20vh;
}
header,
footer,
article,
nav,
aside {
padding: 1em;
}
#media screen and (min-width: 576px) {
#main {
flex-direction: row;
}
#main>nav,
#main>aside {
flex: 0 0 20vw;
}
}
<header>This is a Navbar</header>
<div id="main">
<article><img src="https://upload.wikimedia.org/wikipedia/commons/e/eb/Ash_Tree_-_geograph.org.uk_-_590710.jpg" alt=""></article>
<nav>hello</nav>
<aside>world</aside>
</div>
<footer>Footer content</footer>
.flex {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
max-width: 100%;
min-width: 200px;
flex-wrap: wrap; // this will move your content to new line if there is less space
}
I'm trying to achieve the sticky footer (flexbox version). However, I'm unable to find a working solution if I also want the ability to have scrollable content inside a flex: 1 div (which requires parents to have height: 100%).
Here's a fiddle to demonstrate the problem:
https://jsfiddle.net/gfaqLh42/6/
As you can see, the red area is scrollable (with a min-height: 300px). Notice the footer is offscreen even though the viewport is not less than the red area's min-height + blue area.
Is there a way to do a sticky footer and still use flexbox flex: 1 with scrollable content?
Update
Here's another picture to represent the other big problem I face in trying to make this work:
Is there a way to do a sticky footer and still use flexbox flex: 1
with scrollable content?
Yes, and what you need is to use Flexbox all the way.
So instead of using min-height/height on article-1/card, change their CSS to this:
.article-1 {
flex: 1;
display: flex;
min-height: 0; /* added, i.a Firefox need this */
}
.card {
overflow: auto;
}
Note, I also remove some properties not needed, mainly as they were set to their defaults, and added some. And why the need of min-width, is well explained here:
Why don't flex items shrink past content size?
Updated fiddle
Stack snippet
html, body{
height: 100%;
margin: 0;
font-weight: bold;
}
.header {
position: absolute;
height: 40px;
background-color: grey;
z-index: 1;
width: 100%;
}
.content {
display: flex;
flex-direction: column;
height: 100%;
padding-top: 40px;
box-sizing: border-box; /* added */
}
.wrap {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0; /* added, i.a Firefox need this */
}
.container {
flex: 1;
padding: 10px;
box-sizing: border-box; /* added */
display: flex;
flex-direction: column;
min-height: 0; /* added, i.a Firefox need this */
}
.article-1 {
flex: 1;
display: flex;
min-height: 0; /* added, i.a Firefox need this */
}
.card {
overflow: auto;
}
.card-text {
height: 2000px;
width: 2000px;
background-color: red;
}
.article-2 {
flex: none;
height: 40px;
background-color: blue;
}
.footer {
position: relative;
height: 40px;
background-color: grey;
}
<div class="header">Header</div>
<div class="content">
<div class="wrap">
<div class="container">
<div class="article-1">
<div class="card">
<div class="card-text">
scrollable flex: 1 div<br>
1. scrollable<br>
2. scrollable<br>
3. scrollable<br>
4. etc...
</div>
</div>
</div>
<div class="article-2">
flex: none div
</div>
</div>
</div>
<div class="footer">Footer</div>
</div>
Updated based on a comment
If there is a need for the article-1 to have a minimum height, and to avoid absolute positioning on it, a minimum height could be set on content as well, to push the footer further down on smaller screens.
Updated fiddle 2
Stack snippet
html, body{
height: 100%;
margin: 0;
font-weight: bold;
}
.header {
position: absolute;
height: 40px;
background-color: grey;
z-index: 1;
width: 100%;
}
.content {
display: flex;
flex-direction: column;
height: 100%;
min-height: 450px; /* added */
padding-top: 40px;
box-sizing: border-box; /* added */
}
.wrap {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0; /* i.a Firefox need this */
}
.container {
flex: 1;
padding: 10px;
box-sizing: border-box; /* added */
display: flex;
flex-direction: column;
min-height: 0; /* i.a Firefox need this */
}
.article-1 {
flex: 1;
display: flex;
min-height: 300px; /* changed */
}
.card {
overflow: auto;
}
.card-text {
height: 2000px;
width: 2000px;
background-color: red;
}
.article-2 {
flex: none;
height: 40px;
background-color: blue;
}
.footer {
position: relative;
height: 40px;
background-color: grey;
}
<div class="header">Header</div>
<div class="content">
<div class="wrap">
<div class="container">
<div class="article-1">
<div class="card">
<div class="card-text">
scrollable flex: 1 div<br>
1. scrollable<br>
2. scrollable<br>
3. scrollable<br>
4. etc...
</div>
</div>
</div>
<div class="article-2">
flex: none div
</div>
</div>
</div>
<div class="footer">Footer</div>
</div>
I have a container #container with a fixed width which has three children .item. Each have a flex-basis of 100px but the middle one .item-grow should grow so that all width of #container is used. This works like expected.
The .item-grow element has another children #too-big which could be wider than the available parent's width so its wrapped inside a #scroll container with overflow: scroll and max-width: 100%.
The problem is, that #scroll ignores max-width: 100% and forces the parent .item-grow to grow which foces the container #container to grow.
If I set max-width: 296px instead of max-width: 100% it works but I am looking for a dynamic (and CSS only) solution.
In the embedded code you can find a slider which changes the size of #too-big, you can clearly see that its growing the parents and not using the scrollbars.
function setSize (newValue) {
let elem = document.getElementById("too-big");
elem.setAttribute("style",`width:${newValue}px; height:${newValue}px`);
}
#container {
background-color: red;
display: flex;
width: 500px;
height: 200px;
}
.item {
display: flex;
flex-grow: 0;
flex-shrink: 0;
flex-basis: 100px;
border: solid black 1px;
background-color: gray;
}
.item-grow {
flex-grow: 1;
align-items: center;
}
.center-wrapper {
margin: auto;
display: table-cell;
}
.scroll {
overflow: scroll;
max-width: 100%; /* I want this respect the current parents width */
/* max-width: 296px; */
max-height: 100%; /* I want this respect the current parents height */
/* max-height: 200px; */
}
#too-big {
background-color: green;
width: 100px;
height: 100px;
}
<input type="range" min=100 max=500 value=100 oninput="setSize(this.value)" onchange="setSize(this.value)">
<div id="container">
<div class="item"></div>
<div class="item item-grow">
<div class="center-wrapper">
<div class="scroll">
<div id="too-big"></div>
</div>
</div>
</div>
<div class="item"></div>
</div>
To make that work you can drop the center-wrapper and set margin: auto on the scroll.
Then the item need min-width: 0 to allow it to be smaller than its content.
And for the max-height: 100% to work (cross browser), the item-grow need a height.
Fiddle demo (with overflow: auto instead)
Stack snippet
function setSize (newValue) {
let elem = document.getElementById("too-big");
elem.setAttribute("style",`width:${newValue}px; height:${newValue}px`);
}
#container {
background-color: red;
display: flex;
width: 500px;
height: 200px;
}
.item {
display: flex;
flex-basis: 100px;
border: solid black 1px;
background-color: gray;
box-sizing: border-box; /* added */
min-width: 0; /* added */
}
.item-grow {
flex-grow: 1;
align-items: center;
height: 100%; /* added */
}
.scroll {
margin: auto; /* added */
overflow: scroll;
max-width: 100%;
max-height: 100%;
}
#too-big {
background-color: green;
width: 100px;
height: 100px;
}
<input type="range" min=100 max=500 value=100 oninput="setSize(this.value)" onchange="setSize(this.value)">
<div id="container">
<div class="item"></div>
<div class="item item-grow">
<div class="scroll">
<div id="too-big"></div>
</div>
</div>
<div class="item"></div>
</div>
If I make a flexbox with 2 children and column flow and set the second child to flex-grow 1 the second child expands to fill the flexbox. This works
(ps: Didn't want to clutter the example with safari support so use Chrome or Firefox)
* {
box-sizing: border-box;
}
html, body {
margin: 0;
width: 100%;
height: 100%;
color: white;
}
#outer {
display: flex;
flex-flow: column;
height: 100%;
}
#top {
background-color: red;
}
#bottom {
background-color: blue;
flex: 1 0 auto;
}
<div id="outer">
<div id="top">top</div>
<div id="bottom">bottom (blue)</div>
</div>
But, if I then put a child #inside inside #bottom and set its height to 100% it doesn't increase its height to match even though the flexbox has stretched #bottom.
added css
#inside {
background-color: green;
height: 100%;
}
html
<div id="outer">
<div id="top">top</div>
<div id="bottom">
<div id="inside">inside</div> <!- added ->
</div>
</div>
* {
box-sizing: border-box;
}
html, body {
margin: 0;
width: 100%;
height: 100%;
color: white;
}
#outer {
display: flex;
flex-flow: column;
height: 100%;
}
#top {
background-color: red;
}
#bottom {
background-color: blue;
flex: 1 0 auto;
}
#inside {
background-color: green;
height: 100%;
}
<div id="outer">
<div id="top">top</div>
<div id="bottom">
<div id="inside">inside (green)</div>
</div>
</div>
So I add a height: 100% to #bottom but now bottom is as big as #outer instead of the flex stretched size.
#bottom {
background-color: blue;
flex: 1 0 auto;
height: 100%; /* added */
}
* {
box-sizing: border-box;
}
html, body {
margin: 0;
width: 100%;
height: 100%;
color: white;
}
#outer {
display: flex;
flex-flow: column;
height: 100%;
}
#top {
background-color: red;
}
#bottom {
background-color: blue;
flex: 1 0 auto;
height: 100%;
}
#inside {
background-color: green;
height: 100%;
}
<div id="outer">
<div id="top">top</div>
<div id="bottom">
<div id="inside">inside (green) (would not scroll if working)</div>
</div>
</div>
How do I get #bottom to stretch to fit the flexbox and also get a the child #inside to be 100% height of its container #bottom?
Flex has a quirk where you need to set the height to 0.
Change the #bottom rule's height property to this height: 0;
For the inside to work I changed it to "position: absolute" and as well added a position:relative to the bottom
Update
If you don't want to use absolute position, you can set these 2 css rules like this:
(Note though, that this propagates the original issue if a new inner div is used like the first one)
#bottom {
position: relative;
background-color: blue;
flex: 1 0 auto;
height: 0;
display: flex;
}
#inside {
background-color: green;
flex: 1 0 auto;
}
Sample using "position: absolute"
* {
box-sizing: border-box;
}
html, body {
margin: 0;
width: 100%;
height: 100%;
color: white;
}
#outer {
display: flex;
flex-flow: column;
height: 100%;
}
#top {
background-color: red;
}
#bottom {
position: relative;
background-color: blue;
flex: 1 0 auto;
height: 0;
}
#inside {
background-color: green;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
<div id="outer">
<div id="top">top</div>
<div id="bottom">
<div id="inside">inside (would not scroll if working)</div>
</div>
</div>
How do I get #bottom to stretch to fit the flexbox and also get a the child #inside to be 100% height of its container #bottom?
Just add two lines of code to the CSS.
CSS
#bottom {
background-color: blue;
flex: 1 0 auto;
display: flex; /* NEW */
}
#inside {
flex: 1; /* NEW */
background-color: green;
}
DEMO: http://jsfiddle.net/wf2L8dse/
Here's what's happening:
You have a flex container (#outer) with two flex items (#top and #bottom).
#outer is in column alignment.
#bottom has flex: 1 (i.e., flex-grow: 1), so it occupies all available height in the container.
A new element (#inside) is made a child of #bottom and must occupy the same height as parent.
Solution:
Make #bottom a (nested) flexbox. This activates default flex rules.
One such rule is align-items: stretch, which tells flex items (#inside) to stretch the full height of their container. (Height, in this case, because the flex-direction is row, by default.)
Then apply flex: 1 (or flex-grow: 1) to #inside, so it expands the full width of the container.
Addressing the height: 100% issue
I'm not sure there's anything wrong with your code. You have applied height: 100% to #inside and, as required by the spec when using percentage heights, specified a height for all parent elements including body and the root element (html).
The only thing you may want to consider (to remove the vertical scrollbar on the browser window), is applying overflow: hidden to body.
DEMO: http://jsfiddle.net/wf2L8dse/1/