CSS grid with scrollable row that expands - html

I have a css grid with a single column and two rows.
I want the first row to expand when there is space and to scroll when there is no space.
I want the second row to remain at the bottom of the div.
I have tried using display: flex; in the first row.
HTML:
<div class="parent">
<span class="top">my<br>long<br>long<br>long<br>content</span>
<span class="bottom">always visible</span>
</div>
CSS:
body {
overflow-y: hidden;
}
.parent {
display: grid;
grid-template-columns: auto;
grid-template-rows: 1fr 0fr;
}
.top {
overflow-y: scroll;
display: flex;
flex-direction: column;
}
span {
border: solid;
}
https://jsfiddle.net/9re86f2a/
I expect the first row to scroll when there is no space, but it actually remains the same size.
I expect the first row to expand when there is space, but it actually remains the same size.

I would use the flex model for this kind of behavior even if grid does it fine ;)
html * {
box-sizing:border-box;
padding:0.25em;
margin:0;
}
body {
height:100vh;
}
.parent {
display: flex;
flex-direction: column;
max-height: 100%;
overflow: hidden;
}
.top {
flex-grow: 1;
overflow: auto;
/* style aside */
background:lightblue;
margin:2px;/* or 0 */
border:solid;
}
.bottom {
flex-shrink: 0;
/* style aside */
background:tomato;
margin:2px;/* or 0 */
border:solid;
}
/* demo purpose */
.top .demo {
display: block;
max-height: 0;
overflow: hidden;
transition: 1.5s;
}
.top:hover .demo {
max-height: 300vh;
}
<div class="parent">
<span class="top"><b>Hover me to show my long content</b>
<span class="demo"><br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content</span>
</span>
<span class="bottom">always visible</span>
</div>

There's nothing in your code that triggers an overflow condition.
In order for content to overflow, it needs to exceed a width or height limitation (e.g. height: 300px), which then triggers the scrollbar.
From MDN:
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.
In other words, without a fixed height, you won't get the vertical scrollbar... in Chrome! Ironically, however, in Firefox, a product of MDN, and Edge, the MDN rule above doesn't apply, and your layout works just fine.
.parent {
display: grid;
grid-template-columns: auto;
grid-template-rows: 1fr 0fr;
grid-gap: 2px;
background-color: black;
height: 100vh;
}
.top {
overflow-y: auto;
}
span {
background-color: white;
}
body {
margin: 0;
}
<div class="parent">
<span class="top">my<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content<br>long<br>long<br>long<br>content</span>
<span class="bottom">always visible</span>
</div>
jsFiddle demo
Note on browser rendering differences: I can only speculate as to why Firefox and Edge render a scrollbar on a block-level container that doesn't have a defined height or max-height, as specified by MDN (see above). They could be engaging in an intervention or may have a different interpretation of the specification than the MDN contributors.

Related

CSS Flex - how do I make div content scrollable?

I'm trying to create a sidebar which has 2 boxes. Each box can contain any number of list items(could be hundreds of items), but it's likely that the first will contain only a few items, and the second will contain many more.
I have the following (created in ReactJS):
<div class="drawer">
<div class="drawerContainer">
<div class="sideboxWrapper">
<SideBox header="Box 1" />
<SideBox header="Box 2" />
</div>
</div>
</div>
Each SideBox generates the following structure:
<div class="sidebox">
<h2 class="boxheader">{header}</h2>
<div class="boxContent">
<ul class="sideboxList">
<li class="listItemText listItemTextCurrent">Item 1</li>
<li class="listItemText listItemTextCurrent">Item 2</li>
... more items ...
</ul>
</div>
</div>
So, first of all, I want the boxes to fill the sidebar, but not overflow it. If the boxes are short enough to both fit, then they should both take up as much space as they need, and any remaining space is left blank. If they don't both fit, then they should take up the available space, and the boxContent div should become scrollable.
.drawer {
flex: 0 0 240px;
height: 100%;
overflow-y: hidden;
padding: 10px 0;
}
.drawerContainer {
height: inherit;
overflow: hidden;
padding-bottom: 10px;
padding-top: 10px;
}
.sideboxWrapper {
display: flex;
flex-direction: column;
height: inherit;
overflow: hidden;
}
.sidebox {
margin: 10px;
padding: 0px;
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
}
.boxContent {
overflow-y: auto;
}
The problem I'm having is that when one of the lists is very long, the sideboxes just expand to fit it, even if that means going outside the sideboxWrapper (the overflow is hidden, so the part of the sidebox outside the sideboxWrapper can't be read). What I want to happen is that the flex layout arranges the sideboxes to fit inside the sideboxWrapper, and then the boxheaders are shown and the boxContent is resized (with scrollbars) to fit within the remaining space. So, a long list will have scrollbars.
How can I achieve this?
here is what could be your CSS , I added border so you can see where stands some of your boxes. Using flexbox on so many levels requires to follow clearly what you are doing and see where flex is avalaible and needed.
* {
box-sizing: border-box;
}
.drawer {
width: 240px;/* without a flex parent that works too */
height: 100vh;/* example for a full screen height */
overflow-y: hidden;
padding: 10px 0;
border: solid;
}
.drawerContainer {
height: 100%;
padding-bottom: 10px;
padding-top: 10px;
}
.sideboxWrapper {
display: flex;
flex-direction: column;
height: 100%;
border: solid green;
overflow: hidden;
}
.sidebox {
margin: 10px;
padding: 0px;
flex-basis: 50%;/* make it half */
display: flex;
flex-direction: column;
min-height: 0;/* what is needed */
}
.boxContent {
flex-grow: 1;
overflow-y: auto;
}

div take all available height

I'm using the following HTML
<div className="App">
<div className="AppMenu">
Menu
</div>
<div className="AppContainer">
Test
</div>
</div>
and this CSS:
.App {
min-height: 100vh !important;
max-height: 100vh !important;
height: 100vh !important;
background-color: red;
}
.AppMenu {
background-color: blue;
position: sticky;
top: 0px;
width: 100%;
font-size: 1.3em;
}
.AppContainer {
background-color: green;
}
how can I set / calc AppContainer size to take all heigh => 100vh-(AppMenu height)
with CSS (or js) ?
You already using the full height of a screen with 100vh. There is no need to use a nuke like !important which nearly always just mask the issue instead of solving it. Also min-height: 100vh; + max-height: 100vh; can be considered as bad coding. In this case you want a definite height of 100vh which is done by height: 100vh;. So you having 3 lines of code where you actually only would need 1.
The issue that you get a scrollbar and the screen is overflowing is caused by the default body margin. The element will be 100vh tall and use the default body amrgin which will cause an document height of more then 100vh. Therefor simply reset the default body amrgin to 0 with: body { margin: 0; }
However with that soultion you will have a potencial overflow issue. So you should either set an overflow rule to the container or use min-height instead.
To have the the AppContainer fill the remaining height there are multiple ways to solve it. The easiest way to solve it would be the sue of a CSS-Grid with grid-template-rows: min-content auto;. That way, the Menu will take up as much space as needed and the remining height will be used be the AppContainer.
body {
margin: 0;
}
.App {
height: 100vh;
background-color: red;
display: grid;
grid-template-rows: min-content auto;
}
.AppMenu {
background-color: blue;
position: sticky;
top: 0px;
font-size: 1.3em;
}
.AppContainer {
background-color: green;
}
<div class="App">
<div class="AppMenu">
Menu
</div>
<div class="AppContainer">
Test
</div>
</div>
Last but not least. for HTML you have to use class not className which would be invalid HTML as this attribute doesnt exist.
These lines make no sense. This can be removed:
max-height: 100vh !important;
height: 100vh !important;
To stretch .AppContainer to the full free height, use rule flex: 1:
.AppContainer {
...
flex: 1;
}
And for the .App, set the flex rules. Like this:
.App {
...
display: flex;
flex-direction: column;
}
Flex has very good browser support.
Do you need such a result?
.App {
min-height: 100vh !important;
background-color: red;
display: flex;
flex-direction: column;
}
.AppMenu {
background-color: blue;
position: sticky;
top: 0px;
width: 100%;
font-size: 1.3em;
}
.AppContainer {
background-color: green;
flex: 1;
}
<div class="App">
<div class="AppMenu">
Menu
</div>
<div class="AppContainer">
Test
</div>
</div>

Display:flex and scrolling inner divs

https://jsfiddle.net/wqmm0kxb/5/
html:
<div class="full">
<header><h1>header stuff</h1></header>
<section>
<div>
{lots and lots of content}
</div>
<div>b</div>
<div>c</div>
</section>
</div>
css:
.full {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
header {
flex: 78px 0 0;
background: #ececec;
color: black;
padding-left: 33px;
}
section {
flex: auto 1 1;
display: flex;
align-items: stretch;
> div {
flex: auto 1 1;
overflow-y: auto;
}
}
}
My outer container, '.full', takes up the full width and height of the screen, and uses display:flex to make sure that the header + section children stretch to take up all the space beneath them.
Now, what I want is naturally for the header to take up 78px and the section to take up {full height - 78px} -- but without doing anything like calc preferrably. And I want to be able to scroll in the div children of section, without scrolling affecting the other divs or the page as a whole.
This works perfectly in Chrome, but open up my fiddle in firefox, edge, ie and it doesn't work as expected. Section gets the height of {lots and lots of content} rather than only taking the remaining space of '.full'
What should I do to achieve the Chrome-like layout that I'm expecting?
Apply the overflow-y:auto for your section also, that will fix the issue in IE and Firefox.
section {
flex: auto 1 1;
display: flex;
align-items: stretch;
overflow-y: auto;
> div {
flex: auto 1 1;
overflow-y: auto;
}
}
Fiddle DEMO

get the height of the previous element

Can i get the height of the previous element using only CSS?
I am using calc() function to set dynamically height of the div B.
#b{
height:calc(100vh - heightOfPreviousElement);
}
I need to know the height of the previous element.
what i know is that, 100vh is equal to 100% of the screen height.
I used the code in the answer below.Using flex,
I have one problem. The height of the color orange become smaller.
You can easily achieve the effect you're looking for using flexbox. The trick is to allow the blue container (the one with the flexible height) to grow in size whenever the need arises, using flex: 1 1 auto, which is simply a shorthand for:
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;
See proof-of-concept code snippet below:
body {
padding: 0;
margin: 0;
}
.wrapper {
display: flex;
flex-direction: column;
flex-wrap: no-wrap;
align-items: center;
justify-content: center;
height: 100vh;
}
.wrapper > div {
width: 100%;
}
#c1 {
background-color: #880015;
color: #fff;
height: 60px;
margin-bottom: 10px;
}
#c2 {
background-color: #ff7f27;
}
#c3 {
background-color: #00a2e8;
flex: 1 1 auto;
margin-bottom: 10px;
}
<div class="wrapper">
<div id="c1">height: 60px</div>
<div id="c2">height: auto (determined by content?)</div>
<div id="c3">flexible height</div>
</div>
No you can't select a previous element in CSS.
You might be interested in JQuery Prev OR Parents method for selecting previous element and apply height using .css() method?

Height is not correct in flexbox items in Chrome [duplicate]

This question already has answers here:
Chrome / Safari not filling 100% height of flex parent
(5 answers)
Closed 6 years ago.
I've got a delicate problem for any CSS guru out there.
My green div has a flexible height, taking up the remaining.
And now I want to put a div inside that div which should be the half of the green div. But it seems like if Chrome treats it like half of the whole page rather than the flex item.
http://jsfiddle.net/unh5rw9t/1/
HTML
<body>
<div id="wrapper">
<div id="menu">
1
</div>
<div id="content">2
<div id="half_of_content">2.1</div>
</div>
<div id="footer" style="">
3
</div>
</div>
</body>
CSS
html,body {
height: 100%;
margin: 0;
}
#wrapper {
display: flex;
flex-flow: column;
height: 100%;
}
#menu {
height: 70px;
background-color: purple
}
#content {
flex: 1;
height: 100%;
background-color: green;
}
#half_of_content {
height: 50%;
background-color: yellow;
}
#footer {
height: 100px;
background-color: cyan
}
#Michael_B explained why Chrome behaves like this:
You gave the body a height: 100%. Then gave its child (.wrapper)
a height: 100%. Then gave its child (.content) a height: 100%.
So they're all equal height. Giving the next child (#half_of_content) a height: 50% would naturally be a 50% height
of body.
However, Firefox disagrees because, in fact, that height: 100% of .content is ignored and its height is calculated according to flex: 1.
That is, Chrome resolves the percentage with respect to the value of parent's height property. Firefox does it with respect to the resolved flexible height of the parent.
The right behavior is the Firefox's one. According to Definite and Indefinite Sizes,
If a percentage is going to be resolved against a flex item’s
main size, and the flex item has a definite flex
basis, and the flex container has a definite main
size, the flex item’s main size must be treated as
definite for the purpose of resolving the percentage, and the
percentage must resolve against the flexed main size of the
flex item (that is, after the layout algorithm below has been
completed for the flex item’s flex container, and the flex
item has acquired its final size).
Here is a workaround for Chrome:
#content {
display: flex;
flex-direction: column;
}
#content::after {
content: '';
flex: 1;
}
#half_of_content {
flex: 1;
height: auto;
}
This way the available space in #content will be distributed equally among #half_of_content and the ::after pseudo-element.
Assuming #content doesn't have other content, #half_of_content will be 50%. In your example you have a 2 in there, so it will be a bit less that 50%.
html,
body {
height: 100%;
margin: 0;
}
#wrapper {
display: flex;
flex-flow: column;
height: 100%;
}
#menu {
height: 70px;
background-color: purple
}
#content {
flex: 1;
height: 100%;
background-color: green;
display: flex;
flex-direction: column;
}
#content::after {
content: '';
flex: 1;
}
#half_of_content {
flex: 1;
background-color: yellow;
}
#footer {
height: 100px;
background-color: cyan
}
<div id="wrapper">
<div id="menu">
1
</div>
<div id="content">2
<div id="half_of_content">2.1</div>
</div>
<div id="footer" style="">
3
</div>
</div>
You could absolutely position div id="half_of_content".
#content {
flex: 1;
height: 100%;
background-color: green;
position: relative; /* new */
}
#half_of_content {
height: 50%;
background-color: yellow;
position: absolute; /* new */
width: 100%; /* new */
}
DEMO
With regard to your statement:
But it seems like if Chrome treats it like half of the whole page
rather than the flex item.
You gave the body a height: 100%. Then gave its child (.wrapper) a height: 100%. Then gave its child (.content) a height: 100%. So they're all equal height. Giving the next child (#half_of_content) a height: 50% would naturally be 50% height of body.
With absolute positioning, however, you don't need to specify parent heights.
Nesting flexboxes is a little buggy. I reworked your markup a little by adding an inner wrapper with display: flex; which seems to do the job. Here is the fiddle (also using class names instead of ids).
<div class="content">
<div class="wrapper-inner">
2
<div class="half">
2.1
</div>
</div>
</div>
.wrapper-inner {
position: absolute;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
Fix:
on #content set
display: flex;
flex-flow: column nowrap;
justify-content: flex-end
on #half_of_content set flex: 0 0 50%;
Caveat: you need to add an extra div as a child of #content.
Here's the full example:
html,body {
height: 100%;
margin: 0;
}
#wrapper {
display: flex;
flex-flow: column;
height: 100%;
}
#menu {
height: 70px;
background-color: purple
}
#content {
flex: 1;
height: 100%;
display:flex;
flex-flow: column nowrap;
justify-content: flex-end;
background-color: green;
}
#half_of_content {
flex: 0 0 50%;
background-color: yellow;
}
#footer {
height: 100px;
background-color: cyan
}
<body>
<div id="wrapper">
<div id="menu">
1
</div>
<div id="content">2
<div id="half_of_content">2.1</div>
</div>
<div id="footer" style="">
3
</div>
</div>
</body>