Flex css layout with fixed and variable elements - css

I am hoping to create the following layout in pure CSS. I know that I can achieve this with a JavaScript solution, but a CSS solution would be much cleaner, if it is possible.
I have created a jsFiddle which I know is incorrect, to provide a starting point. The HTML and CSS I use in the jsFiddle are shown below.
Notes:
I would like this to fill the full height of the window, so that there is no scroll bar for the page (but see my last point)
There are two sections that can contain a variable number of elements.
The red elements are images which the user can add on the fly, and which will be given a frame with a fixed aspect ratio (shown here as a square)
The green section will contain a list which will have at least one item, so it will have a fixed minimum height. It may have up to four items, so its height may change. I would prefer not to have this section scroll. If the user makes the window too short for both the green and the blue elements to show full height, then the page as a whole will have to scroll.
My question is: can this be done in pure CSS? If you know that there is a solution, and if you can provide some pointers as to how I can achieve it, then I can continue to work towards that solution. If you know that there is no solution, then I will simply adopt a JavaScript approach.
If there is a solution, and you would be happy to share it, then I will be delighted that you have saved me a lot of time.
<!DOCTYPE html>
<html>
<head>
<title>Flex</title>
<style>
body, html {
height: 100%;
margin: 0;
background: #000;
}
main {
width: 30em;
height: 100%;
margin: 0 auto;
background: #333;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
.head{
width:100%;
-webkit-flex: 3em;
flex: 3em;
background: #fcc;
}
.expand{
width:100%;
overflow:auto;
}
.filler {
width:100%;
height:20em;
background: #003;
border-bottom: 1px solid #fff;
}
.space {
width:100%;
height:10em;
border-bottom: 1px solid #fff;
}
.foot{
width:100%;
-webkit-flex: 0 0 2em;
flex: 0 0 2em;
background: #cfc;
}
</style>
</head>
<body>
<main>
<div class="head">HEAD</div>
<div class="expand">
<div class="space"></div>
<div class="filler"></div>
<div class="space"></div>
</div>
<div class="foot">FOOT</div>
</main>
</body>
</html>

If I understand it well,
main {
height: auto;
min-height: 100%;
}
.head {
min-height: 3em;
}
.foot {
min-height: 2em;
}
.expand {
flex-basis: 0; /* Initial height */
flex-grow: 1; /* Grow as much as possible */
overflow: auto;
}
body,
html {
height: 100%;
margin: 0;
background: #000;
}
main {
width: 20em;
min-height: 100%;
margin: 0 auto;
background: #333;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
.head {
width: 100%;
min-height: 3em;
background: #fcc;
}
.expand {
width: 100%;
flex-basis: 0;
flex-grow: 1;
overflow: auto;
}
.filler {
width: 100%;
height: 20em;
background: #003;
border-bottom: 1px solid #fff;
}
.space {
width: 100%;
height: 2em;
border-bottom: 1px solid #fff;
}
.foot {
width: 100%;
min-height: 2em;
background: #cfc;
}
<main>
<div class="head">HEAD</div>
<div class="expand">
<div class="space"></div>
<div class="filler"></div>
<div class="space"></div>
</div>
<div class="foot">FOOT</div>
</main>

Related

CSS: flex-grow maintain vertical aspect ratio

I am creating a website with a circular menu. The website content should fit all onto the homepage without the need to scroll. The menu needs to fill the remaining space on the homepage. However, I am unsure how to maintain the shape of the circle while filling the remaining space on the homepage using flex-grow: 1. Is there a way I can do this with pure CSS? Setting the menu to a set viewport size is not acceptable, it needs to fill the remaining space. I am not having luck using the traditional padding-top: 100% to maintain aspect ratio. The circle is not quite circular and it takes up twice the remaining space.
body {
height: 100vh;
display: flex;
flex-direction: column;
margin: 0;
}
#title {
font-size: 30px;
}
#circle {
background: black;
border-radius: 50%;
flex-grow: 1;
padding-top: 100%;
}
#footer {
background: black;
color: white;
}
<body>
<div id="title">Title</div>
<div>navigation</div>
<div id="circle"></div>
<div id="footer">Footer</div>
</body>
Edit
I have figured out a way to maintain the aspect ratio of the circle filling the remaining space with flex grow. However, it is what I would consider a hack so I am leaving this question open.
body {
height: 100vh;
display: flex;
flex-direction: column;
margin: 0;
}
#title {
font-size: 30px;
}
#circle {
background: black;
border-radius: 50%;
flex-grow: 1;
/*width: max-content;*/
padding: 0%;
align-self: center;
}
#circle img {
height: 100%;
width: auto;
justify-content: center;
}
#footer {
background: black;
color: white;
}
<body>
<div id="title">Title</div>
<div>navigation</div>
<div id="circle"><img src="https://luxury.zappos.com/search/imgs/blank.20190219170746.png"></div>
<div id="footer">Footer</div>
</body>
Edit 2
It seems I was mislead by caniuse.com. This solution does not seem to work in most browsers besides chrome. Is there another solution?
Put the circle div inside a wrapper div in your HTML:
<div id="circle-wrap">
<div id="circle"></div>
</div>
Then move the flex rule to the wrapper:
#circle-wrap {
flex-grow: 1;
}
body {
height: 100vh;
display: flex;
flex-direction: column;
margin: 0;
}
#title {
font-size: 30px;
}
#circle-wrap {
flex-grow: 1;
}
#circle {
background: black;
border-radius: 50%;
padding-top: 100%;
}
#footer {
background: black;
color: white;
}
<body>
<div id="title">Title</div>
<div>navigation</div>
<div id="circle-wrap">
<div id="circle"></div>
</div>
<div id="footer">Footer</div>
</body>

Why does eliminating scrollbars leave a gap?

There is a mysterious gap at the bottom of the ".one" column.
I gathered that this results from requesting no scroll bars.
Yet somehow the vertical scrollbar disappears entirely, but the horizontal scroll disappears while leaving a gap in its place.
What is this gap and how do I get rid of it?
d3.select('.one')
.selectAll('div')
.data(d3.range(40))
.enter()
.append('div')
.attr('class', 'picture box')
.append('h2')
.text(d => d);
html, body {
width: 100%; height: 100%; margin: 0;
}
.container {
width: 100%; height: 100%;
display: grid;
grid-template-columns: 10%;
}
.box {
background-color: #484848;
color: #fff;
border-radius: 5px;
padding: 1px; margin: 1px;
}
.menu {
text-align: center;
overflow: scroll;
}
.menu::-webkit-scrollbar {
width: 0 !important;
}
.one { grid-column: 1; grid-row: 1; }
.two { grid-column: 2; grid-row: 1; }
div.picture {
box-sizing: content-box;
max-width: 100%;
border: 2px solid gray;
border-radius: 5px;
background-color: #222;
display: flex;
justify-content: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div class="box one menu noscrollbar">
<h2>One</h2>
</div>
<div class="box two menu noscrollbar">
<h2>Two</h2>
</div>
</div>
It's because when you allow it to scroll, it's leaving a place for the horizontal scrollbar. Tell it to only scroll on the y-axis (up and down) with overflow-y: scroll in the css.
From Mozilla:
Content is clipped if necessary to fit the padding box. Browsers always display scrollbars whether or not any content is actually clipped, preventing scrollbars from appearing or disappearing as content changes. Printers may still print overflowing content.
Full page explainer on overflow: https://developer.mozilla.org/en-US/docs/Web/CSS/overflow
See solution below:
d3.select('.one')
.selectAll('div')
.data(d3.range(40))
.enter()
.append('div')
.attr('class', 'picture box')
.append('h2')
.text(d => d);
html, body {
width: 100%; height: 100%; margin: 0;
}
.container {
width: 100%; height: 100%;
display: grid;
grid-template-columns: 10%;
}
.box {
background-color: #484848;
color: #fff;
border-radius: 5px;
padding: 1px; margin: 1px;
}
.menu {
text-align: center;
overflow-y: scroll;
}
.menu::-webkit-scrollbar {
width: 0 !important;
}
.one { grid-column: 1; grid-row: 1; }
.two { grid-column: 2; grid-row: 1; }
div.picture {
box-sizing: content-box;
max-width: 100%;
border: 2px solid gray;
border-radius: 5px;
background-color: #222;
display: flex;
justify-content: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div class="box one menu noscrollbar">
<h2>One</h2>
</div>
<div class="box two menu noscrollbar">
<h2>Two</h2>
</div>
</div>
It would help to know which browser you’re seeing this problem in. In Firefox on a Mac with hidden scrollbars there is no gap at the bottom of the .one column.
However, you could try overflow-y: scroll on .menu, instead of overflow as this will only scroll the container along the y-axis. overflow-x is, of course, the companion.

flexbox how to achieve this specific layout?

Like in the image - http://i65.tinypic.com/aa7ndw.png Examples and live flex configurators are explain only simple examples, or I just don't get it.
Will I be able to use media queries to for example not display a4 when < 800px?
I have always used float and flex is somehow 'different' anyway I would like to know it better, so any help is appreciated.
flex specific example
Apply display: flex to a container and its child elements will be displayed in flex. For this layout, you will want to wrap the elements when width is already filled for the current row.
The header and footer will be width: 100%, taking a full row. #a3 and #a4 will have flex: 1 to distribute the width of their row, taking each one 50% of the width.
div.flex-container{
width: 200px;
height: 300px;
background-color: black;
display: flex;
flex-flow: row wrap;
}
#a1, #a2{
width: 100%;
}
#a3, #a4{
flex: 1;
}
#a5, #a6, #a7{
height: 50px;
width: 80%;
margin: auto;
margin-bottom: 1rem;
}
/* Example styles */
div{
text-align: center;
}
#a1{
background-color: red;
}
#a2{
background-color: limegreen;
}
#a3{
background-color: royalblue;
}
#a4{
background-color: cyan;
}
#a5, #a6, #a7{
background-color: fuchsia;
}
<div class="flex-container">
<div id="a1">a1</div>
<div id="a3">a3</div>
<div id="a4">a4
<div id="a5">a5</div>
<div id="a6">a6</div>
<div id="a7">a7</div>
</div>
<div id="a2">a2</div>
</div>
And yeah, you can use media queries as normal
div.flex-container{
width: 200px;
height: 300px;
background-color: black;
display: flex;
flex-flow: row wrap;
}
#a1, #a2{
width: 100%;
}
#a3, #a4{
flex: 1;
}
#a5, #a6, #a7{
height: 50px;
width: 80%;
margin: auto;
margin-bottom: 1rem;
}
#media (max-width: 800px){
#a4{
display: none;
}
}
/* Example styles */
div{
text-align: center;
}
#a1{
background-color: red;
}
#a2{
background-color: limegreen;
}
#a3{
background-color: royalblue;
}
#a4{
background-color: cyan;
}
#a5, #a6, #a7{
background-color: fuchsia;
}
<div class="flex-container">
<div id="a1">a1</div>
<div id="a3">a3</div>
<div id="a4">a4
<div id="a5">a5</div>
<div id="a6">a6</div>
<div id="a7">a7</div>
</div>
<div id="a2">a2</div>
</div>

"inline-flex" item does not grow with its content in Internet Explorer

I have a simple table structure made up of divs:
$(document).ready(function() {
$("button").on("click", function() {
$(".cell").outerWidth(500);
})
})
div {
border: 1px solid black;
box-sizing: border-box;
}
.container {
width: 400px;
height: 100%;
padding: 5px;
overflow: auto;
}
.row {
min-width: 100%;
width: auto;
display: inline-flex;
padding: 5px;
margin-bottom: 5px;
border-color: red;
}
.cell {
flex: 0 0 auto;
border-right: 1px solid black;
overflow: hidden;
min-width: 200px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="row">
<div class="cell">x</div>
</div>
<div class="row">
<div class="cell">x</div>
</div>
<div class="row">
<div class="cell">x</div>
</div>
</div>
<button type="button">Change width</button>
The rows need to be vertically stacked, each having the (unknown) height of their content and be at least as wide as the container. The container has to scroll if the content does not fit. The width of the cells will be interactively changed using JS and the rows should expand to fit the whole content. For this reason, the rows have the following style:
.row {
min-width: 100%;
width: auto;
display: inline-flex;
}
The flex part is needed for the cells and is outside of the scope of this question. Being an inline element, the row will grow with the content in all major browsers but not in Internet Explorer 11. Check out the fiddle and click the button to change the width of the cells. The border helps to visualize the behaviour. The image below shows the expected behaviour (top) and how Internet Explorer interprets it (bottom):
What kind of bug is this (couldn't figure it out from the list of flexbugs) and how can I make it work in Internet Explorer?
In IE11 the behavior is as wanted:
The default flex behavior of flex items has changed. In Internet
Explorer 10, flex items that didn't fit their containers overflowed
the margins of the container or clipped to the margins of the
container. Starting with IE11, these items now shrink to fit their
containers (up to the min-width value, if specified). Use the
flex-shrink property to change this behavior.
https://msdn.microsoft.com/en-us/library/dn265027(v=vs.85).aspx
So, the following .cell rules should solve the issue
.cell {
flex: 0 0 auto;
-ms-flex: 0 1 auto; /* overwrites the previous rule only in IE11 */
border-right: 1px solid black;
overflow: hidden;
min-width: 200px;
}
Here's a solution I've come up with... that doesn't use Flex at all.
Updated:
Simplified the CSS to handle the margins and padding better. When you click the button to make the cell grow larger, because of the fixed width of the container, there is no margin between the row and the container.
$(document).ready(function() {
$("button").on("click", function() {
$(".cell").width(500);
})
})
html, body { width: 100%; height: 100%; margin:0; padding:0; }
div {
border: 1px solid black;
/* box-sizing: border-box; */
}
.container {
width: 400px;
padding: 5px;
margin:10px;
background: green;
overflow: auto;
}
.container::after, .row::after {
content: " ";
visibility: hidden;
display: block;
height: 0;
width: 0;
clear:both;
}
.row {
min-width: calc(100% - 22px);
padding: 5px;
margin: 5px;
border-color: red;
background: pink;
float:left;
}
.container > *:last-child {
/* margin: 0; */
}
.cell {
padding: 5px;
margin:5px;
border-right: 1px solid black;
overflow: hidden;
width: calc(200px - 22px);
background: orange;
float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="row">
<div class="cell">x</div>
</div>
<div class="row">
<div class="cell">x</div>
</div>
<div class="row">
<div class="cell">x</div>
</div>
</div>
<button type="button">Change width</button>
The problem:
div {
box-sizing: border-box;
}
.cell {
flex: 0 0 auto;
}
The solution:
If you don't want to change this part of css, i suggest you to avoid setting width, instead of setting min-width
$(document).ready(function() {
$("button").on("click", function() {
$(".cell").css("min-width","500px");
})
})

How to position header, sidebox and mainbox?

Demo: http://jsfiddle.net/zz1ou0bv/
HTML:
<div class="header">header</div>
<div class="sidebox">sidebox</div>
CSS:
.header {
background-color: red;
height:60px;
}
.sidebox {
width: 210px;
background-color: blue;
color: black;
position: absolute;
bottom: 0;
top: 0;
}
How do I position the blue box just under the red box? I could for example add top: 68px; to the .sidebox to fix the problem but is there any other way to position it automatically, I would like to change the header height without being forced to change the top tag in sidebox to make it fit.
How do I position a brand new div that takes up the WHOLE white area under the red box and besides blue box? This should be automatic in case header/sidebox changes height/width. The green content should be replaced with this new div: http://i.gyazo.com/a41107cb7c1844b439f045ad85d40aec.png
If you always want the sidebar to occupy 100% of the window you could try this approach:
html, body { height: 100%; }
.header {
background-color: red;
height:60px;
}
.sidebox {
width: 210px;
background-color: blue;
color: black;
bottom: 0;
top: 0;
min-height: 100%;
}
http://jsfiddle.net/zz1ou0bv/2/
Here's my attempt: http://jsfiddle.net/rL8z9bm0/ .
Regarding your second question you should always have some kind of relation between the blue and yellow boxes, either pixels or percentage (better)
.sidebox {
width: 30%;
...
}
.content {
left:30%;
...
}
Seems like a good use case for the flexbox here:
Fiddle with Flexbox
Here's the relevant source:
HTML
<div class="wrapper">
<div class="header">header</div>
<div class="sidebox">sidebox</div>
<div class="content">content</div>
</div>
CSS
body {
height: 100vh;
margin: 0;
}
.wrapper {
display: flex;
flex-flow: row wrap;
height: 100%;
}
.header {
background-color: red;
height:60px;
flex: 1 100%;
}
.sidebox {
flex: 1 auto;
background-color: blue;
color: white;
}
.content {
flex: 4 auto;
background-color: sienna;
color: white;
height: calc(100% - 60px);
}
Hope that helps.

Resources