I need to detect when a sticky element gets pinned/sticky.
with this approach is working fine if there is only one sticky element at the time.
Note that the trick is setting top: -1px
and checking if element is leaving the viewport
const stickyElements = document.querySelectorAll('.sticky-element');
const observer = new IntersectionObserver(
([entry]) => {
entry.target.classList.toggle('pinned', entry.intersectionRatio < 1);
},
{ threshold: [1, 0.01] },
);
stickyElements.forEach((sticky, i) => {
if (sticky) {
observer.observe(sticky);
}
});
.sticky-element {
position: sticky;
top: -1px;
z-index: 2;
}
.sticky-element.pinned {
background: white;
border-bottom: 1px solid #ccc;
}
.sticky-element.pinned {
&:after {
content: ' (pinned)';
}
}
.just-content {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: #f8f8f8;
}
<div class="sticky-element">I'm sticky</div>
<div class="just-content">I'm just normal element</div>
<div class="sticky-element">I'm sticky 1</div>
<div class="just-content">I'm just normal element</div>
<div class="sticky-element">I'm sticky 2</div>
<div class="just-content">I'm just normal element</div>
The issue is when you try to get it combined with an sticky element above, lets try it out:
const stickyElements = document.querySelectorAll('.sticky-element');
const observer = new IntersectionObserver(
([entry]) => {
entry.target.classList.toggle('pinned', entry.intersectionRatio < 1);
}, {
threshold: [1, 0.01]
},
);
stickyElements.forEach((sticky, i) => {
if (sticky) {
observer.observe(sticky);
}
});
.sticky-nav {
position: sticky;
top: -1px;
z-index: 2;
outline: 1px dashed black;
}
.sticky-element {
position: sticky;
top: 15px;
z-index: 2;
outline: 1px dashed orange;
}
.sticky-element.pinned {
background: white;
border-bottom: 1px solid #ccc;
}
.sticky-element.pinned {
&:after {
content: ' (pinned)';
}
}
.just-content {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: #f8f8f8;
}
<div class="just-content">I'm just normal element</div>
<nav class="sticky-nav">Sticky element just above</nav>
<div class="just-content">I'm just normal element</div>
<div class="sticky-element">I'm sticky</div>
<div class="just-content">I'm just normal element</div>
<div class="sticky-element">I'm sticky 1</div>
<div class="just-content">I'm just normal element</div>
<div class="sticky-element">I'm sticky 2</div>
<div class="just-content">I'm just normal element</div>
I added outlines so you can check that the items is behind/overlapping the "main" sticky element.
So the observer donesn't work in this scenario because the item is not actually leaving the viewport, i'm guessing.
so question is, any workaround for this scenario? perhaps container based?
I was able to achieve it by comparing the window.getComputedStyle(sticky, null).getPropertyValue('top')
against the css top property value. When they match, is sticky
const stickyElements = document.querySelectorAll('.sticky-element');
const onScroll = () => {
requestAnimationFrame(() => {
stickyElements.forEach((sticky) => {
if (sticky) {
const elementCSSTop = parseInt(window.getComputedStyle(sticky, null).getPropertyValue('top'));
sticky.classList.toggle('pinned', sticky.getBoundingClientRect().top === elementCSSTop);
}
});
});
};
window.addEventListener('scroll', onScroll);
.sticky-nav {
position: sticky;
top: -1px;
z-index: 2;
outline: 1px dashed black;
}
.sticky-element {
position: sticky;
top: 18px;
z-index: 2;
}
.sticky-element.pinned {
background: white;
border-bottom: 1px solid #ccc;
}
.sticky-element.pinned {
&:after {
content: ' (pinned)';
}
}
.just-content {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: #f8f8f8;
}
<div class="just-content">I'm just normal element</div>
<nav class="sticky-nav">Sticky element just above</nav>
<div class="just-content">I'm just normal element</div>
<div class="sticky-element">I'm sticky</div>
<div class="just-content">I'm just normal element</div>
<div class="sticky-element">I'm sticky 1</div>
<div class="just-content">I'm just normal element</div>
<div class="sticky-element">I'm sticky 2</div>
<div class="just-content">I'm just normal element</div>