Files
v1.pivoine.art/packages/hydecorp/push-state/src/scroll.ts
2025-10-25 12:39:30 +02:00

79 lines
1.9 KiB
TypeScript

import { getScrollTop, Cause } from "./common";
interface ScrollState {
[k: string]: any;
scrollTop?: number;
scrollHeight?: number;
}
export class ScrollManager {
private parent: { histId: string } & HTMLElement;
constructor(parent: { histId: string } & HTMLElement) {
this.parent = parent;
if ("scrollRestoration" in history) {
history.scrollRestoration = "manual";
}
}
manageScrollPosition({ cause, url: { hash } }: { cause: Cause; url: URL }) {
switch (cause) {
case Cause.Push: {
// FIXME: make configurable
this.scrollHashIntoView(hash, {
behavior: "smooth",
block: "start",
inline: "nearest",
});
break;
}
case Cause.Pop: {
this.restoreScrollPosition();
break;
}
case Cause.Init: {
this.restoreScrollPositionOnReload();
break;
}
}
}
private elementFromHash(hash: string) {
return document.getElementById(decodeURIComponent(hash.substr(1)));
}
private scrollHashIntoView(
hash: string,
options: boolean | ScrollIntoViewOptions,
) {
if (hash) {
const el = this.elementFromHash(hash);
if (el) el.scrollIntoView(options);
} else {
window.scroll(window.pageXOffset, 0);
}
}
private restoreScrollPosition() {
const { histId } = this.parent;
const { scrollTop } =
(history.state && history.state[histId]) || ({} as ScrollState);
if (scrollTop != null) {
window.scroll(window.pageXOffset, scrollTop);
}
}
private restoreScrollPositionOnReload() {
const { histId } = this.parent;
const scrollState = history.state && history.state[histId];
// FIXME: As far as I can tell there is no better way of figuring out if the user has scrolled
// and it doesn't work on hash links b/c the scroll position is going to be non-null by definition
if (scrollState && getScrollTop() === 0) {
this.restoreScrollPosition();
} else if (location.hash) {
requestAnimationFrame(() => this.scrollHashIntoView(location.hash, true));
}
}
}