Feature request: ability to pass in an HTMLElement to use as reference, instead of window
Currently, the component always uses window to listen & react to scroll events. In some applications (e.g., Next.js), what scrolls is actually a specific div (e.g., div id="__next" ...) in the case of Next.js.
It would be great to be able to pass in an HTML element for this component to listen/react to, and set scroll position of.
I've done the following for my project:
import React, { useState, useEffect } from "react";
import {FaArrowUp} from "react-icons/fa";
type ScrollToTopProps = React.HTMLAttributes<HTMLButtonElement> & {
scrollingElement?: HTMLElement | undefined;
top?: number;
smooth?: boolean;
icon?: React.ReactNode;
ariaLabelText?: string;
};
function scrollToTop(smooth = false, scrollingElement: HTMLElement | undefined) {
if(!scrollingElement) {
if (smooth) {
window.scrollTo({
top: 0,
behavior: "smooth",
});
return;
}
document.documentElement.scrollTop = 0;
} else {
if (smooth) {
scrollingElement.scrollTo({
top: 0,
behavior: "smooth",
});
return;
}
scrollingElement.scrollTop = 0;
}
}
const ScrollToTop = ({
scrollingElement,
top = 20,
className = "scroll-to-top",
icon,
ariaLabelText = "Scroll to top",
smooth = false,
}: ScrollToTopProps) => {
const [visible, setVisible] = useState(false);
const onScroll = () => {
if(!scrollingElement) {
setVisible(document.documentElement.scrollTop > top);
} else {
setVisible(scrollingElement.scrollTop > top);
}
};
useEffect(() => {
if(!scrollingElement) {
document.addEventListener("scroll", onScroll);
} else {
//const scrollingElement = document.getElementById("__next")!;
scrollingElement.addEventListener("scroll", onScroll);
}
// Remove listener on unmount
return () => {
if(!scrollingElement) {
document.removeEventListener("scroll", onScroll);
} else {
scrollingElement.removeEventListener("scroll", onScroll);
}
}
});
return <>
{visible && (
<button
className={className}
onClick={() => scrollToTop(smooth, scrollingElement)}
aria-label={ariaLabelText}
>
{icon || (
<FaArrowUp />
)}
</button>
)}
</>;
};
export default ScrollToTop;
I then use it as follows:
<ScrollToTop smooth={true} scrollingElement={IS_BROWSER? document.getElementById("__next")!: undefined} className="scroll-to-top" icon={<FaArrowUp className="w-full h-full text-white" />} />
Hi, thank you for the request + the star! 😄 I will look in to this. Is there something not working as expected when using Nextjs? AFAIK the button appears after scroll etc.
Well, no idea if it's specific to Next.js, but for my site, document.documentElement.scrollTop always seemed to remain at 0. The element that did scroll was the __next wrapper div added by Next.js.
You can find the sources of my site here if you want to test this behavior: https://github.com/dsebastien/website-dsebastien
It's also possible that I created the issue with my bad CSS skills ;-)
Sorry for the late reply, I see. Feel free to submit a PR if you'd like! 😄