Reputation: 319
I have a navigation sidebar that I would like to stick to the top of the page as the user scrolls down. If there are too many links to fit in the viewport, I'd like the element to overflow so users can scroll inside it to click links and also stick to the bottom of the viewport so all links are accessible.
I can achieve this by using position: sticky
to stick the element to the top of the viewport, then using getBoundingClientRect()
to constrain it's max-height
based on its current position. Like this:
#element {
/* Stick the element to the top of the viewport as the user scrolls down */
position: sticky;
top: 0;
/* Overflow by scrolling within the element */
overflow-y: auto;
}
document.addEventListener('scroll', () => {
// Keep the element entirely in the viewport (so users can access all links by scrolling)
// by limiting the element's max-height.
const element = document.getElementById('element');
const elementTop = element.getBoundingClientRect().top;
element.style.maxHeight = 'calc(100vh - ' + elementTop + 'px);';
});
This does work, but the call to getBoundingClientRect()
is expensive, especially on every scroll. Throttling the event introduces distracting screen jitter.
Is there a pure CSS way to achieve this?
EDIT: This JsFiddle demonstrates what I want to achieve: https://jsfiddle.net/8mt7az0g/1/
Note that initially the left nav ends at the bottom of the viewport. As you scroll down, the left nav grows in height until it reaches the top. But it remains pinned to the bottom.
Upvotes: 0
Views: 2363
Reputation: 11047
I'm a little bit confused by the ask, but here goes for a sticky sidebar:
I'm just using position: sticky
, with top: 0
and a max-height: 100vh
.
If this doesn't work for you, it'll be a lot easier to get towards an answer that works for you if you include a snippet that runs so we can see what you've tried to do and work from there.
body {
/*I have this set so that there isn't any top/bottom margin so 100vh actually corresponds to the size of the page*
*Some caveats here for mobile browsers being implemented weirdly.
*/
margin: 0;
}
#container {
/* This sets up the columns so that the divs will render side by side. First column is the width of the navbar (max-content), second is the rest of the page */
display: grid;
grid-template-columns: max-content 1fr;
}
#element {
/* Stick the element to the top of the viewport as the user scrolls down */
position: sticky;
top: 0;
/* Overflow by scrolling within the element */
overflow-y: auto;
/* make the navbar have a max height based on the screen size */
max-height: 100vh;
/* so that we can see the navbar's size */
background-color: teal;
}
#content {
/* I'm too lazy to get some massive lorem ipsum text, so here's some fake content for the page*/
height: 300vh;
}
<div id="container">
<div id="element">
<button>Test</button><br/>
<button>Test2</button><br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/> Test
<br/> Test2
<br/>
</div>
<div id="content">Really long content here?</div>
</div>
Upvotes: 1