import { useEffect, useRef, useState } from "react";
const HEADER_HEIGHT = 64;
const BREADCRUMBS_HEIGHT = 61;
const OFFSET = 0;
function App() {
const stickyRef = useRef(null);
const [isStuck, setIsStuck] = useState(false);
useEffect(() => {
const stickyElement = stickyRef.current as unknown as HTMLDivElement;
if (!stickyElement) return;
const sentinel = document.createElement("div");
sentinel.style.height = "1px";
sentinel.style.position = "absolute";
sentinel.style.top = `${HEADER_HEIGHT + BREADCRUMBS_HEIGHT - OFFSET}px`;
sentinel.style.left = "0";
sentinel.style.right = "0";
sentinel.style.pointerEvents = "none";
sentinel.style.backgroundColor = "red";
sentinel.style.border = "1px dashed red";
sentinel.style.zIndex = "1";
stickyElement.parentNode?.insertBefore(sentinel, stickyElement);
const observer = new IntersectionObserver(
([entry]) => {
setIsStuck(!entry.isIntersecting);
},
{
root: null,
rootMargin: "0px",
threshold: 0,
}
);
observer.observe(sentinel);
return () => {
observer.disconnect();
if (sentinel.parentNode) {
sentinel.parentNode.removeChild(sentinel);
}
};
}, []);
return (
<>
<div
style={{
height: HEADER_HEIGHT,
backgroundColor: "lightgreen",
color: "#000000bd",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
Header
</div>
<div
style={{
height: BREADCRUMBS_HEIGHT,
backgroundColor: "lightpink",
color: "#000000bd",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
Navbar
</div>
<div
ref={stickyRef}
style={{
backgroundColor: "lightgray",
height: isStuck ? 60 : 300,
position: "sticky",
top: 0,
transition: "height 0.3s ease",
color: "#000000bd",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
Sticky
</div>
<p style={{ margin: 0, padding: 16 }}>
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Ratione
ducimus nam a id ipsam, eum nihil minus ab dolorem exercitationem,
praesentium tempora pariatur ullam? Asperiores optio eos consequuntur
temporibus omnis, incidunt quia aspernatur autem mollitia. Alias
distinctio minima, quos repellat ipsa nesciunt quisquam amet cupiditate
perspiciatis sit optio reprehenderit esse, porro explicabo. Fuga iure,
iste doloribus corrupti pariatur accusamus, quia aspernatur quibusdam
facilis deserunt qui debitis accusantium consequuntur! Iusto repellendus
rem sint voluptatibus ipsam itaque deleniti mollitia repellat ratione
recusandae! Veritatis placeat soluta odio nihil, aliquid dolor iste
dolorem facilis laudantium id tempora nostrum, ducimus possimus
recusandae dicta porro enim sed sapiente explicabo incidunt repudiandae
modi. Distinctio explicabo id, repudiandae saepe totam eligendi facere
cum repellat voluptates aliquid adipisci quod ipsam ipsa dicta expedita
dolor non aperiam accusamus consequuntur, maiores dolores fugit? Nam
earum sapiente debitis! Odio exercitationem placeat iusto, nihil magnam,
accusamus itaque nam amet culpa distinctio aliquam quis expedita
suscipit eligendi temporibus ea aut veniam quia, ab eum labore corrupti
cupiditate ipsa!
</p>
</>
);
}
export default App;
In the above code I want to collapse the sticky section when it is stuck to the top of the viewport. To do that I used the IntersectionObserver browser API.
In your browser, open responsive mode, select iPhone SE (375px * 667px). Then if you scroll down you will see the multiple collapsing and expanding of the sticky section!
I attempted to set a guard for setIsStuck based on the height of the p tag. It works, but I don’t like this solution.
I want a clean solution that doesn’t depend on p tag section.
CodeSandbox Link