Restore native mobile page scrolling
This commit is contained in:
@@ -1,29 +1,68 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { ArrowDown } from "lucide-react";
|
||||
|
||||
function resolveScrollTarget() {
|
||||
const mainContent = document.getElementById("main-content");
|
||||
|
||||
if (mainContent instanceof HTMLElement) {
|
||||
const overflowY = window.getComputedStyle(mainContent).overflowY;
|
||||
const usesOwnScroll =
|
||||
(overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay")
|
||||
&& mainContent.scrollHeight > mainContent.clientHeight + 1;
|
||||
|
||||
if (usesOwnScroll) {
|
||||
return { type: "element" as const, element: mainContent };
|
||||
}
|
||||
}
|
||||
|
||||
return { type: "window" as const };
|
||||
}
|
||||
|
||||
function distanceFromBottom(target: ReturnType<typeof resolveScrollTarget>) {
|
||||
if (target.type === "element") {
|
||||
return target.element.scrollHeight - target.element.scrollTop - target.element.clientHeight;
|
||||
}
|
||||
|
||||
const scroller = document.scrollingElement ?? document.documentElement;
|
||||
return scroller.scrollHeight - window.scrollY - window.innerHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Floating scroll-to-bottom button that appears when the user is far from the
|
||||
* bottom of the `#main-content` scroll container. Hides when within 300px of
|
||||
* the bottom. Positioned to avoid the mobile bottom nav.
|
||||
* Floating scroll-to-bottom button that follows the active page scroller.
|
||||
* On desktop that is `#main-content`; on mobile it falls back to window/page scroll.
|
||||
*/
|
||||
export function ScrollToBottom() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const el = document.getElementById("main-content");
|
||||
if (!el) return;
|
||||
const check = () => {
|
||||
const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||
setVisible(distance > 300);
|
||||
setVisible(distanceFromBottom(resolveScrollTarget()) > 300);
|
||||
};
|
||||
|
||||
const mainContent = document.getElementById("main-content");
|
||||
|
||||
check();
|
||||
el.addEventListener("scroll", check, { passive: true });
|
||||
return () => el.removeEventListener("scroll", check);
|
||||
mainContent?.addEventListener("scroll", check, { passive: true });
|
||||
window.addEventListener("scroll", check, { passive: true });
|
||||
window.addEventListener("resize", check);
|
||||
|
||||
return () => {
|
||||
mainContent?.removeEventListener("scroll", check);
|
||||
window.removeEventListener("scroll", check);
|
||||
window.removeEventListener("resize", check);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const scroll = useCallback(() => {
|
||||
const el = document.getElementById("main-content");
|
||||
if (el) el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
|
||||
const target = resolveScrollTarget();
|
||||
|
||||
if (target.type === "element") {
|
||||
target.element.scrollTo({ top: target.element.scrollHeight, behavior: "smooth" });
|
||||
return;
|
||||
}
|
||||
|
||||
const scroller = document.scrollingElement ?? document.documentElement;
|
||||
window.scrollTo({ top: scroller.scrollHeight, behavior: "smooth" });
|
||||
}, []);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
Reference in New Issue
Block a user