How do I wait for scroll to be restored?
Answered
Hairy Woodpecker posted this in #help-forum
Hairy WoodpeckerOP
I've been tasked with added a fancy effect to a nav bar so it's different at the top of certain pages. I need to track if the page is scrolled, which I monitor with a setInterval. However, I get flickering because the scroll jumps to 0 and is then restored during navigation.
I need to know in my setInterval when a) nativagation is in progress and/or b) when scroll has been restored.
I need to know in my setInterval when a) nativagation is in progress and/or b) when scroll has been restored.
Answered by GravityExploitz ✦
Use scroll events + debounce instead of setInterval, and tie it to useRouterState in a useEffect so it re-runs when navigation finishes.
7 Replies
@Hairy Woodpecker I've been tasked with added a fancy effect to a nav bar so it's different at the top of certain pages. I need to track if the page is scrolled, which I monitor with a setInterval. However, I get flickering because the scroll jumps to 0 and is then restored during navigation.
I need to know in my setInterval when a) nativagation is in progress and/or b) when scroll has been restored.
Use scroll events + debounce instead of setInterval, and tie it to useRouterState in a useEffect so it re-runs when navigation finishes.
Answer
Hairy WoodpeckerOP
I've got something working but still with intervals:
import { usePathname } from "next/navigation";
import { useEffect, useRef, useState } from "react";
export const useIsScrolled = (): boolean => {
const pathname = usePathname();
const prevPathname = useRef(pathname);
const [isScrolled, setIsScrolled] = useState(false);
useEffect(() => {
let update: (timestamp: number) => void;
const updateIsScrolled = () => {
setIsScrolled(window.scrollY !== 0);
};
if (pathname === prevPathname.current) {
update = updateIsScrolled;
} else {
prevPathname.current = pathname;
let prevY = window.scrollY;
let changedAt = -Infinity;
update = (timestamp: number) => {
const y = window.scrollY;
if (y === prevY) {
if (timestamp - changedAt > 100) {
update = updateIsScrolled;
}
} else {
prevY = y;
changedAt = timestamp;
}
};
}
let rafId: number | undefined;
const handleAnimationFrame = (timestamp: number) => {
rafId = undefined;
update(timestamp);
rafId = requestAnimationFrame(handleAnimationFrame);
};
rafId = requestAnimationFrame(handleAnimationFrame);
return () => {
if (rafId !== undefined) {
cancelAnimationFrame(rafId);
rafId = undefined;
}
};
}, [pathname]);
return isScrolled;
};Do you think there's much point which to scroll events?
I think I've done what you're suggesting except with animation frames instead of scroll events.
rAF is fine, scroll events aren’t worth switching to here.
Hairy WoodpeckerOP
Thanks for your help 🙂
Hairy WoodpeckerOP
This didn't work in the end. Here's what actually works:
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";
export const useShowLogo = (): boolean => {
const pathname = usePathname();
const [showLogo, setShowLogo] = useState(pathname === "/");
useEffect(() => {
let id: number | null = null;
const handleAnimationFrame = () => {
id = null;
setShowLogo(window.location.pathname === "/" && window.scrollY === 0);
schedule();
};
const schedule = () => {
id = requestAnimationFrame(handleAnimationFrame);
};
schedule();
return () => {
if (id !== null) {
cancelAnimationFrame(id);
id = null;
}
};
}, []);
return showLogo;
};