I need to apply a CSS class to a position:sticky
element when it has become stuck. I’ve got this working when I use a top position, but I’m having trouble figuring out how to determine this for a bottom position. I suppose I need to take the height into account somewhere, but I’m just not sure what the best thing would be here.
I also need this to work with offsets, just just a top or bottom position of 0
. Here’s what I have so far
const stickyElements = [...document.querySelectorAll(".sticky")];
window.addEventListener("scroll", () => {
stickyElements.forEach((el) => toggleClassIfStuck(el))
});
window.dispatchEvent(new Event('scroll')); //trigger initially
function toggleClassIfStuck(el){
const computedStyles = getComputedStyle(el);
if (this.canBeStuck(computedStyles)) {
const hasTopPositionSet = computedStyles.top !== 'auto';
const hasBottomPositionSet = computedStyles.bottom !== 'auto';
if (hasTopPositionSet || hasBottomPositionSet) {
el.classList.toggle('is-stuck', this.isStuck(el, computedStyles, hasBottomPositionSet))
}
}
}
function canBeStuck(computedStyles) {
return computedStyles.display !== 'none' && computedStyles.position === 'sticky';
}
function isStuck(el, computedStyles, shouldUseBottomPosition) {
const offsetParent = el.offsetParent; //the element which this element is relatively sticky to
const rect = el.getBoundingClientRect();
const parentRect = offsetParent.getBoundingClientRect();
if (shouldUseBottomPosition) {
//this isn't correct, but not sure what to check here!
const elBottom = parseInt(computedStyles.bottom, 10);
return rect.top - rect.bottom === elBottom;
} else {
const elTop = parseInt(computedStyles.top,10);
return rect.top === elTop;
}
}
.sticky {
position:sticky;
background: #EEE;
padding: .5rem;
border: 1px solid #DDD;
transition: all 200ms;
}
.sticky-top { top:0; }
.sticky-top-offset { top: 1rem;}
.sticky-bottom { bottom: 0; }
.sticky-bottom-offset { bottom: 1rem; }
.is-stuck{
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.25);
background: lightskyblue;
}
main{ display: flex; gap:.5rem;}
section{ height:120vh; width: 40%; }
<main>
<section>
<br>
<div id="one" class="sticky sticky-top">Top</div>
<br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br>
<div class="sticky sticky-bottom">Bottom</div>
<br>
</section>
<section>
<br><br><br><br>
<div class="sticky sticky-top-offset">Top with offset</div>
<br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br>
<div class="sticky sticky-bottom-offset">Bottom with offset</div>
<br><br><br><br>
</section>
</main>