Improve scroll listener (#2692)

This commit is contained in:
Greg Bergé
2025-01-08 11:17:22 +01:00
committed by GitHub
parent b950a64406
commit 44a20fe5ee
3 changed files with 58 additions and 10 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'gitbook': patch
---
Improve smoothness of scroll listener
@@ -2,11 +2,13 @@
import { Icon } from '@gitbook/icons';
import { usePathname } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useLanguage, tString } from '@/intl/client';
import { tcls } from '@/lib/tailwind';
import { useScrollListener } from '../hooks/useScrollListener';
const globalClassName = 'navigation-open';
/**
@@ -28,26 +30,20 @@ export function HeaderMobileMenu(props: Partial<React.ButtonHTMLAttributes<HTMLB
}
};
const handleScroll = () => {
const windowRef = useRef(window);
useScrollListener(() => {
if (window.scrollY >= scrollDistance) {
setHasScrolled(true);
} else {
setHasScrolled(false);
}
};
}, windowRef);
// Close the navigation when navigating to a page
useEffect(() => {
document.body.classList.remove(globalClassName);
}, [pathname]);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<button
{...props}
@@ -0,0 +1,47 @@
'use client';
import { type RefObject, useEffect, useLayoutEffect, useRef } from 'react';
/**
* Listen for scroll events on an element in a performant way.
*
* @example
* const Example = () => {
* const ref = useRef(null);
* useScrollListener((event) => {
* console.log('scroll', event);
* }, ref);
*
* return <div ref={ref} />;
* }
*/
export function useScrollListener(
listener: (event: Event) => void,
elementRef: RefObject<HTMLElement | Window> | null,
) {
const listenerRef = useRef(listener);
useLayoutEffect(() => {
listenerRef.current = listener;
});
useEffect(() => {
const element = elementRef?.current;
if (!element) {
return undefined;
}
let ticking = false;
const listener = (ev: Event) => {
if (ticking) {
return;
}
requestAnimationFrame(() => {
listenerRef.current(ev);
ticking = false;
});
ticking = true;
};
element.addEventListener('scroll', listener, { passive: true });
return () => {
element.removeEventListener('scroll', listener);
};
}, [elementRef]);
}