I have a simple calendar view which statically can be coded as the React component below. There is a parent container and it contains two containers timeslot
and days
. I want them next to each other so I used flex property. Now I want container to be some fixed size and want the overflow items to scroll. When I scroll I want headers to stick. The timeHeader
and dayHeader
stops sticking after I scroll 40% of the vertical length.
import React from "react";
import "./Test.css";
function Test() {
return (
<div className="parent">
<div className="timeslot">
<div className="timeHeader">Time</div>
<div>
<ul>
<li>7:00 a.m</li>
<li>8:00 a.m</li>
<li>9:00 a.m</li>
<li>10:00 a.m</li>
<li>11:00 a.m</li>
<li>12:00 p.m</li>
<li>1:00 p.m</li>
<li>2:00 p.m</li>
<li>3:00 p.m</li>
<li>4:00 p.m</li>
<li>5:00 p.m</li>
<li>6:00 p.m</li>
<li>7:00 p.m</li>
<li>8:00 p.m</li>
<li>9:00 p.m</li>
<li>10:00 p.m</li>
</ul>
</div>
</div>
<div className="days">
<div className="dayHeader">Days</div>
<div className="dayslots">
<div>
<ul>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
</ul>
</div>
<div>
<ul>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
</ul>
</div>
<div>
<ul>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
</ul>
</div>
<div>
<ul>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
</ul>
</div>
</div>
</div>
</div>
);
}
export default Test;
and its CSS
.parent {
display: flex;
width: 400px;
height: 100px;
overflow: scroll;
}
.timeHeader {
position: sticky;
top: 0;
background-color: white;
}
.timeslot {
position: sticky;
left: 0;
z-index: 2;
background: white;
}
.days {
flex: 5;
}
li {
min-width: 100px;
}
.dayslots {
display: flex;
}
.dayHeader {
background-color: white;
position: sticky;
top: 0;
}
The data inside the li tag, you can ignore it, it could be anything. All I want to fix is Time Header to stick all the time. Day header to stick when I scroll vertically and time i.e 7:00am... to stick when I scroll horizontally.
Now I want container to be some fixed size.
For flex items to shrink over their content size to fit parent
, set the the childs' min-width (or height) to 0, i.e., add min-width:0
on the timeslot
and days
container to allow them to fit the parent's width:400px
.
The timeHeader and dayHeader stops sticking after I scroll 40%.
position:sticky
makes the header to offset relative to its nearest scrolling ancestor (.parent) as expected. The reason it stops working after scrolling 40% is because the header is treated as "stuck" only before it meets the opposite edge of its containing block.
I want the overflow items to scroll. When I scroll I want headers to stick.
It's not clear how you want the overflow to look like, but instead of adding overflow
on parent
, it makes more sense to have each field scrolls individually. This can be achieved by adding overflow-y:scroll
(or auto
) to the two long lists instead. (.dayslots
and time list container.) If you only want one scroll on parent
, move it onto parent
instead.
Next, these two lists have to shrink to fit into parent
so that overflow will work. This can be done by flex-box flex-direction:column
, and again we need to add min-height:0
on the lists to allow them to shrink over their content size.
The modified html and css will look like this: (There are tricks to hide the scroll bars, but they are outside the scope of this question)
.parent {
display: flex;
width: 400px;
height: 150px;
/*remove this since display:flex is shrinking its contents, there won't be any overflow*/
/*move overflow here if you want to scroll all childs under .parent together*/
/* overflow: scroll;*/
}
.timeslot {
/*removed because not needed anymore*/
/* position: sticky;*/
left: 0;
z-index: 2;
background: white;
/* Allow it to shrink over its content width by .parent's display:flex*/
min-width: 0;
/*add the following 2 lines align the time header and contents*/
display: flex;
flex-direction: column;
}
.timeHeader,
.dayHeader {
/* removed because not needed anymore*/
/* position: sticky; */
/* top: 0; */
background-color: white;
}
.timeslots,
.dayslots {
/*Again, allow it to shrink over its content height*/
min-height: 0;
overflow-y: scroll;
display: flex;
}
/* Below CSS changed with the same logic above*/
.days {
min-width: 0;
flex: 5;
display: flex;
flex-direction: column;
}
li {
min-width: 100px;
}
<div class="parent">
<div class="timeslot">
<div class="timeHeader">Time</div>
<div class="timeslots">
<ul>
<li>7:00 a.m</li>
<li>8:00 a.m</li>
<li>9:00 a.m</li>
<li>10:00 a.m</li>
<li>11:00 a.m</li>
<li>12:00 p.m</li>
<li>1:00 p.m</li>
<li>2:00 p.m</li>
<li>3:00 p.m</li>
<li>4:00 p.m</li>
<li>5:00 p.m</li>
<li>6:00 p.m</li>
<li>7:00 p.m</li>
<li>8:00 p.m</li>
<li>9:00 p.m</li>
<li>10:00 p.m</li>
</ul>
</div>
</div>
<div class="days">
<div class="dayHeader">Days</div>
<div class="dayslots">
<div>
<ul>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
</ul>
</div>
<div>
<ul>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
<li>2</li>
</ul>
</div>
<div>
<ul>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
<li>3</li>
</ul>
</div>
<div>
<ul>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
<li>4</li>
</ul>
</div>
</div>
</div>
</div>