79 lines
1.9 KiB
TypeScript
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));
|
|
}
|
|
}
|
|
}
|