Vertically center element with dynamic height in scroll container - css

I have a scroll container that's usually the size of the whole screen. Inside of it I place dynamic content. So I won't know which height it has or how many elements will be inserted.
Now I want to layout it like this:
if there is enough space, I want the whole content vertically centered inside the scroll container
if the total height of the content exceeds the height of the scroll container, I want the container to just scroll the contents like there was no centering.
I created an example where I tried to solve this problem with flexbox. With content height less than the container height it works like intended. But when the content exceeds the container height, due to justify-content, some elements of the content are cut off:
You can see on the image that the scroll container's scrollTop is all the way at the top, yet elements 1 & 2 aren't visible.
I'd like to know if there is a CSS only solution. A JS solution I could do myself but that's not what I'm after. If it's not possible, that's okay too.
.container {
display: inline-block;
width: 300px;
height: 300px;
border: 2px solid red;
overflow-y: auto;
margin: 1rem 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.block {
width: 80%;
height: 3rem;
margin: 1rem auto;
background: blue;
flex-shrink: 0;
color: #fff;
text-align: center;
}
<div class="container">
<div class="block">1</div>
</div>
<div class="container">
<div class="block">1</div>
<div class="block">2</div>
<div class="block">3</div>
</div>
<div class="container">
<div class="block">1</div>
<div class="block">2</div>
<div class="block">3</div>
<div class="block">4</div>
<div class="block">5</div>
<div class="block">6</div>
<div class="block">7</div>
<div class="block">8</div>
</div>

Try applying the overflow to an inner containing div like so:
.container {
display: inline-block;
width: 300px;
height: 300px;
border: 2px solid red;
margin: 1rem 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.inner {
overflow-y: auto;
}
.block {
width: 80%;
height: 3rem;
margin: 1rem auto;
background: blue;
flex-shrink: 0;
color: #fff;
text-align: center;
}
<div class="container">
<div class="inner">
<div class="block">1</div>
</div>
</div>
<div class="container">
<div class="inner">
<div class="block">1</div>
<div class="block">2</div>
<div class="block">3</div>
</div>
</div>
<div class="container">
<div class="inner">
<div class="block">1</div>
<div class="block">2</div>
<div class="block">3</div>
<div class="block">4</div>
<div class="block">5</div>
<div class="block">6</div>
<div class="block">7</div>
<div class="block">8</div>
</div>
</div>

Related

flex-column: how to limit the height of grow part so it does not expand?

I'm sure this kind of question was asked before, but I really can't describe it exactly and concisely enough to let the search engine to understand me. So here we go:
To better explain my question I'm writing the code in tailwind style here. A stack snippet is also attached below:
<div class="root w-screen h-screen flex flex-col">
<div class="header h-[72px] w-full bg-red shrink-0"></div>
<div class="content grow">
<!-- a whole lot of content, very tall, height > 2000 px -->
</div>
</div>
In this example, I would like to limit the height of the entire div.root to 100vh. However, because div.content is very tall, it expands the body that it shows a vertical scrollbar.
Well this is fairly easy to overcome, I only need to add scroll-y-auto to div.content. So the body scrollbar disappears, and div.content shows a vertical scrollbar. Perfect.
However later on, I decided to split div.content into two columns: both column shall have its own vertical scrollbar. Intuitively I changed the code to:
<div class="root w-screen h-screen flex flex-col">
<div class="header h-[72px] w-full bg-red shrink-0"></div>
<div class="content grow">
<div class="left overflow-y-auto">
<!-- a whole lot of content, very tall, height > 2000 px -->
</div>
<div class="right overflow-y-auto">
<!-- a whole lot of content, very tall, height > 2000 px -->
</div>
</div>
</div>
But this does not work at all, as the attached snippet demonstrates. body got its scrollbar back, but not div.left or div.right.
I've explored several ways to solve this problem. In the end the best solution I got was to set the height of div.content to calc(100% - 72px). This works perfectly, but I understand it's only because I know the exact height of div.header is fixed at 72px.
Was I doing something wrong here? What's the most elegant way to solve this kind of problem?
body {
margin: 0;
}
.root {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}
.header {
height: 72px;
width: 100%;
background-color: red;
flex-shrink: 0;
}
.content {
flex-grow: 1;
display: flex;
flex-direction: row;
}
.very-tall-content {
background-color: green;
height: 2400px
}
.left, .right {
flex-grow: 1;
margin: 0 4px;
overflow-y: auto;
}
<div class="root">
<div class="header"></div>
<div class="content">
<div class="left">
<p class="very-tall-content"></p>
</div>
<div class="right">
<p class="very-tall-content"></p>
</div>
</div>
</div>
Allright, try this one maybe it fixed your problem :)
instead of using flex for .root use grid. down here we have a
header with minimum height of 72px and if it's content overloads, the
header will auto-fit them
:root {
--header-min-height: 72px;
}
body {
margin: 0;
}
.root {
display: grid;
grid-template-rows: minmax(var(--header-min-height), auto) 1fr;
height: 100vh;
}
.header {
grid-row: 1;
background: darkcyan;
}
.content {
grid-row: 2;
display: flex;
background-color: palegreen;
overflow-y: hidden;
}
.content>div {
flex-grow: 1;
overflow-y: auto;
}
.white-space {
height: 3000px;
}
<div class="root">
<div class="header"></div>
<div class="content">
<div class="left">
Left Side
<div class="white-space"></div>
Left Side
</div>
<div class="right">
Right Side
<div class="white-space"></div>
Right Side
</div>
</div>
</div>
here's the example if it overloads.
:root {
--header-min-height: 72px;
}
body {
margin: 0;
}
.root {
display: grid;
grid-template-rows: minmax(var(--header-min-height), auto) 1fr;
height: 100vh;
}
.header {
grid-row: 1;
background: darkcyan;
}
.content {
grid-row: 2;
display: flex;
background-color: palegreen;
overflow-y: hidden;
}
.content>div {
flex-grow: 1;
overflow-y: auto;
}
.white-space {
height: 3000px;
}
.row {
display: flex;
flex-direction: row;
width: fit-content;
}
.item {
background: rgba(255, 255, 255, .5);
width: fit-content;
padding: 10px;
margin: 5px;
border-radius: 5px;
}
<div class="root">
<div class="header">
<div class="row">
<div class="item">test</div>
<div class="item">test</div>
<div class="item">test</div>
</div>
<div class="row">
<div class="item">test</div>
<div class="item">test</div>
<div class="item">test</div>
</div>
<div class="row">
<div class="item">test</div>
<div class="item">test</div>
<div class="item">test</div>
</div>
</div>
<div class="content">
<div class="left">
Left Side
<div class="white-space"></div>
Left Side
</div>
<div class="right">
Right Side
<div class="white-space"></div>
Right Side
</div>
</div>
</div>

Nested Flexbox - outer container doesn't flex

Update : I have edited the snippet to show better what I'm trying to achieve...
I have a number of tables of data, each of variable length, on a kiosk display. I want to fill the viewport as columns then overflow to pages below ie paging down would give me next screen of data. I thought Nested Flexbox would allow me to do this but the outer Container doesn't do what I hoped and data just flows to right - see below. Am I just inept or should I be doing it another way? Thx!
.container1 {
background: lightgrey;
display: flex;
width:300px;
flex-direction: column;
flex-wrap:wrap;
}
.container2 {
background: orangered;
display: flex;
flex-direction: column;
height:200px;
width: 300px;
flex-wrap: wrap;
}
.container2 > div{
font-size: 40px;
width: 100px;
}
.green {
background: yellowgreen;
}
.blue {
background: steelblue;
}
My effort doesn't work ...
<div class="container1">
<div class="container2">
<div class="green">1a<br>1b<br>1c</div>
<div class="blue">2a<br></div>
<div class="green">3a<br>3b</div>
<div class="blue">4a<br>4b<br>4c</div>
<div class="green">5a<br>5b</div>
<div class="blue">6a<br></div>
<div class="green">7a<br>7b</div>
<div class="blue">8a<br>8b<br>8c</div>
<div class="green">9a<br>9b<br>9c</div>
<div class="blue">10a<br></div>
<div class="green">11a<br>11b</div>
<div class="blue">12a<br>12b<br>12c</div>
</div>
</div>
I want output like this but ...
<div class="container2">
<div class="green">1a<br>1b<br>1c</div>
<div class="blue">2a<br></div>
<div class="green">3a<br>3b</div>
<div class="blue">4a<br>4b<br>4c</div>
</div>
<div class="container2">
<div class="green">5a<br>5b</div>
<div class="blue">6a<br></div>
<div class="green">7a<br>7b</div>
<div class="blue">8a<br>8b<br>8c</div>
</div>
<div class="container2">
<div class="green">9a<br>9b<br>9c</div>
<div class="blue">10a<br>10a<br></div>
<div class="green">11a<br>11b</div>
<div class="blue">12a<br>12b<br>12c</div>
</div>
You don't want to use flex-direction: column on the inner container2. You still want that to be row.
Setting flex-direction: column only establishes that the direction of children should flow from top to bottom (or reverse with column-reverse).
Setting flex-wrap: wrap on a parent with flex-direction: column wraps the elements on the cross axis (row in this case).
You don't even need the outer parent container, since there was only one flex child container2.
.container2 {
background: orangered;
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 300px;
height: 200px;
overflow: scroll;
}
.container2 > div {
font-size: 40px;
flex: 0 0 33.33333%;
min-height: 200px;
}
.green {
background: yellowgreen;
}
.blue {
background: steelblue;
}
<div class="container2">
<div class="green">1a<br>1b<br>1c</div>
<div class="blue">2a<br></div>
<div class="green">3a<br>3b</div>
<div class="blue">4a<br>4b<br>4c</div>
<div class="green">1a<br>1b<br>1c</div>
<div class="blue">2a<br></div>
<div class="green">3a<br>3b</div>
<div class="blue">4a<br>4b<br>4c</div>
<div class="green">1a<br>1b<br>1c</div>
<div class="blue">2a<br></div>
<div class="green">3a<br>3b</div>
<div class="blue">4a<br>4b<br>4c</div>
</div>

How to Center a list of inline-block items, while aligning the content of the list to the Left?

I have a list of inline-block elements inside a parent element. I wont to center the parent element while keeping the child elements aligned to the left.
<body>
<style>
/* the width of the container is dynamic by the width of the window */
.container {
height: 600px;
text-align: center;
border: 1px solid red;
overflow-y: auto;
}
/* i don't know the width of the list element, it's only for centering */
.list {
text-align: left;
}
/* the width of the item is const - always 200px */
.item {
display: inline-block;
width: 200px;
height: 200px;
margin: 10px;
background: gray;
}
</style>
<div class="container">
<div class="list">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
</div>
</div>
</body>
As you can see in the attached example, the list element isn't centered.
If I remove the align: left style then the list element will be centered but then the item elements will be centered as well (I want them to be aligned to the left)
This what I'm trying to achieve:
Some thing like this just give text-align: left; to item and remove text-align: center; from list
<body>
<style>
.container {
width: 600px;
height: 600px;
text-align: center;
border: 1px solid red;
overflow-y: auto;
}
.item {
display: inline-block;
width: 200px;
height: 200px;
margin: 10px;
background: gray;
text-align: left;
}
</style>
<div class="container">
<div class="list">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
<div class="item">9</div>
</div>
</div>
</body>
make all items fit the width of the list either by putting a fixed width to list or edit the width of item to have less than 50% of list, .item{width: 46%;} worked well
.container {
width: 600px;
height: 900px;
border: 1px solid red;
overflow-y: auto;
}
.list {
border: 1px solid #bbb;
width: 445px;
margin: 0px auto;
}
.item {
text-align: left;
display: inline-block;
width: 200px;
height: 200px;
margin: 10px;
background: gray;
}
<body>
<div class="container">
<div class="list">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
<div class="item">9</div>
</div>
</div>
</body>
What are you actually trying to achieve?
Now you have .container and .list that are both 600px wide, so you don't see if list element is aligned to left or center.
First, you should have different widths for .list and .container. Then your list should be an inline-block element for it to react to text-align: center.
I made a codepen for you to make this more clear: https://codepen.io/leo-melin/pen/MWYbLLa
.container {
width: 800px;
height: 600px;
text-align: center;
border: 1px solid red;
overflow-y: auto;
}
.list {
display: inline-block;
width: 500px;
border: solid 1px #000; /* Added border to make it visual */
text-align: left;
}
.item {
display: inline-block;
width: 200px;
height: 200px;
margin: 10px;
background: gray;
}
UPDATE 1
If I now understand you correctly, you have n-elements per row that are all 200px wide and you want to center them in container if there's not enough space to fit another 200px element. You want the last item to be aligned to left inside the container.
As in this another stackoverflow answer: https://stackoverflow.com/a/40936683/2061685 you probably need to utilize flexbox for that behaviour to work.
So something like this would do:
<div class="container">
<div class="list">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
<div class="item">9</div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
<div class="item phantom"></div>
</div>
</div>
.container {
width: 100%;
height: 600px;
border: 1px solid red;
overflow-y: auto;
text-align: center;
}
.list {
display: inline-flex;
flex-wrap: wrap;
justify-content: center;
text-align: left;
}
.list:after {
content: '';
flex: auto;
}
.item {
display: inline-block;
width: 200px;
height: 200px;
margin: 10px;
background: gray;
}
.item.phantom {
visibility: hidden;
height: 0;
}
Working codepen: https://codepen.io/leo-melin/pen/gObgLrX
As you can see, you need to add those phantom items to make the last element stay on the left. On very wide screen with just a few items, you need to control the amount of phantom elements. Use mediaqueries or define an algorithm while rendering the items to make sure it works in all cases.
Hope this helps now for you to figure out a solution.

Flexbox item does not shrink when only one item [duplicate]

This question already has answers here:
Why don't flex items shrink past content size?
(5 answers)
Closed 3 years ago.
In flexbox, if you set flex-flow: column nowrap and the elements inside have non-zero value of flex shrink, they should shrink down to all fit inside the flex container.
I have found that if you have only one item in this container and it has content bigger than the flex-container then it will not shrink down. But if other elements are included in the container (if it is not the only item) then it will shrink down.
Best visualized in this CodePen.
Here is the same code from the CodePen.
.container {
padding: 10px;
height: 100px;
width: 100px;
background: red;
margin: 5px 20px;
display: flex;
flex-flow: column nowrap;
align-items: center;
}
.item {
width: 80px;
height: 40px;
background: blue;
margin: 5px;
/* same as
flex: 1 1 40px;
*/
}
.super.item {
height: 200px;
}
.item div {
width: 10px;
height: 150px;
background: black;
}
body {
margin: 0;
display: flex;
}
<div class='container'>
<div class='item'></div>
<div class='item'></div>
<div class='item'></div>
<div class='item'>
</div>
</div>
<div class='container'>
<div class='item'></div>
<div class='item'></div>
<div class='item'></div>
<div class='item'>
<div></div>
</div>
</div>
<div class='container'>
<div class='item super'>
</div>
</div>
<div class='container'>
<div class='item super'>
<div></div>
</div>
</div>
Is there an explanation on why that last div does not shrink to fit the flex container?
What about making the height of those contents inherit their parent's one?
.super.item {
height: inherit;
}
.item div {
width: 10px;
height: inherit;
background: black;
}
Snippet below, is that what you're trying to achieve? :
.container {
padding: 10px;
height: 100px;
width: 100px;
background: red;
margin: 5px 20px;
display: flex;
flex-flow: column nowrap;
align-items: center;
}
.item {
width: 80px;
height: 40px;
background: blue;
margin: 5px;
/* same as
flex: 1 1 40px;
*/
}
.super.item {
height: inherit;
}
.item div {
width: 10px;
height: inherit;
background: black;
}
body {
margin: 0;
display: flex;
}
<div class='container'>
<div class='item'></div>
<div class='item'></div>
<div class='item'></div>
<div class='item'>
</div>
</div>
<div class='container'>
<div class='item'></div>
<div class='item'></div>
<div class='item'></div>
<div class='item'>
<div></div>
</div>
</div>
<div class='container'>
<div class='item super'>
</div>
</div>
<div class='container'>
<div class='item super'>
<div></div>
</div>
</div>
I don't know if you are talking about it or not.
Your div is taking width and height as item.
so whenever you add another div it will add height of 150px.
.item div{
width: 10px;
height: 150px;
background: black;
}
Very simple rule for items of a flexbox:
Items grow value is 0 and shrink is 1 and the base-case is auto.
flex: 0 1 auto
So
.item{
width: 80px;
height: 40px; # will be used as base-case
background: blue;
margin: 5px;
/* same as
flex: 1 1 40px;
*/
}
Why does second container's item shrink? Easy. base-case is 40px or on the other hand it dose not have the class .super. Add .super and see what happens.
<div class='container'>
<div class='item'></div>
<div class='item'></div>
<div class='item'></div>
<div class='item'> # flex's item height: 40px;
<div></div> # regular div
</div>
</div>
Why the third container that has the class .super does shrink? while height: 200px and it should overflow? Easy. it is the same as: flex: 0 1 200px (this is not quite right, see the comment below, what #TemaniAfif has pointed out)
try it:
.super.item{
/* height: 200px; */
flex: 0 1 200px; # equal to height: 200px
}
now try this:
.super.item{
/* height: 200px; */
flex: 0 0 200px; # turn off auto grow / shrink
}
Why the fourth overflows?. Easy. the item has a child that is a regular div
<div class='container'>
<div class='item super'> # flex's item
<div></div> # regular div
</div>
</div>
and the height of this div is 150px
.item div{
width: 10px;
height: 150px; # remove this one, it shrinks
background: black;
}

How do I place a div on the right site of a div which has a random width?

I have a div #1 with a variable width and variable height. Now I want to position a div #2 with fixed width and height next to the right site of #1.
These two divs should be inside another div with width: 100%, because I want to repeat those two divs.
Here is an image (white: div #1, black: div #2):
How would I do that?
I played around with floating
Using a flexbox for the rows. I put the width for the white box as inline CSS because I assume it will be calculated somehow in your code.
.container {
background: lightgreen;
padding: 3em;
}
.row {
display: flex;
height: 4em;
}
.row:not(:last-child) {
margin-bottom: 1em;
}
.flexible {
background: white;
}
.fixed {
background: black;
width: 1em;
}
<div class="container">
<div class="row">
<div class="flexible" style="width:150px"></div>
<div class="fixed"></div>
</div>
<div class="row">
<div class="flexible" style="width:500px"></div>
<div class="fixed"></div>
</div>
<div class="row">
<div class="flexible" style="width:50px"></div>
<div class="fixed"></div>
</div>
</div>
Use flex.
.container {
display: flex;
}
.secondDiv {
width: 200px;
}
You can use this example:
.container{
width: 100%;
}
.div1{
width: <div1 width>;
height: <div1 height>;
float: left;
background-color: white;
}
.div2{
float: left;
width: <div2 width>;
height: <div1 height>;
background-color: black;
}
You should group this two divs (div1 and div2) in another div, inside de container with 100% width:
<div id="container" class="container">
<div id="block1" style="float: left; width: 100%">
<div id="div1" class="div1">
</div>
<div id="div2" class="div2">
</div>
</div>
...
</div>

Resources