-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscrollHelper.tsx
101 lines (84 loc) · 3.88 KB
/
scrollHelper.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
type ScrollHelper = {
elementId?: string // if elementId is not provided, then the window is used
onReachingBottom?: () => void
onReachingTop?: () => void
onLeavingBottom?: () => void
onLeavingTop?: () => void
onScroll?: () => void
}
class ScrollLookup {
public static active: { [elementId: string]: ScrollHelper } = {};
public static lookup: {
[elementId: string]: {
scrollFunction?: () => void
isTop: boolean
isBottom: boolean
}
} = {};
}
export function addScroll(scrollInfo: ScrollHelper, timeout?: number) {
//overwrites if same id is given
if (!scrollInfo) return;
if (timeout) {
setTimeout(() => addScroll(scrollInfo), timeout);
return;
}
const lookupKey = scrollInfo.elementId || "@@window@@";
const newElement = !(lookupKey in ScrollLookup.active);
ScrollLookup.active[lookupKey] = {};
ScrollLookup.lookup[lookupKey] = { isBottom: false, isTop: true };
if (scrollInfo.onReachingBottom) ScrollLookup.active[lookupKey].onReachingBottom = scrollInfo.onReachingBottom;
if (scrollInfo.onReachingTop) ScrollLookup.active[lookupKey].onReachingTop = scrollInfo.onReachingTop;
if (scrollInfo.onLeavingBottom) ScrollLookup.active[lookupKey].onLeavingBottom = scrollInfo.onLeavingBottom;
if (scrollInfo.onLeavingTop) ScrollLookup.active[lookupKey].onLeavingTop = scrollInfo.onLeavingTop;
if (scrollInfo.onScroll) ScrollLookup.active[lookupKey].onScroll = scrollInfo.onScroll;
if (Object.keys(ScrollLookup.active[lookupKey]).length === 0) {
delete ScrollLookup.active[lookupKey];
delete ScrollLookup.lookup[lookupKey];
return;
}
if (!newElement) return;
const [listenerTarget, element] = getScrollElementAndTarget(scrollInfo.elementId);
const scrollFunction = () => {
const active = ScrollLookup.active[lookupKey];
if (Math.abs(element.scrollHeight - element.scrollTop - element.clientHeight) < 1) {
ScrollLookup.lookup[lookupKey].isBottom = true;
if (active.onReachingBottom) active.onReachingBottom();
} else if (ScrollLookup.lookup[lookupKey].isBottom) {
ScrollLookup.lookup[lookupKey].isBottom = false;
if (active.onLeavingBottom) active.onLeavingBottom();
}
if (element.scrollTop < 1) {
ScrollLookup.lookup[lookupKey].isTop = true;
if (active.onReachingTop) active.onReachingTop();
} else if (ScrollLookup.lookup[lookupKey].isTop) {
ScrollLookup.lookup[lookupKey].isTop = false;
if (active.onLeavingTop) active.onLeavingTop();
}
if (active.onScroll) active.onScroll();
}
ScrollLookup.lookup[lookupKey].scrollFunction = scrollFunction;
listenerTarget.addEventListener("scroll", scrollFunction, false);
}
function getScrollElementAndTarget(elementId?: string): [HTMLElement | Window, HTMLElement] {
if (!elementId) return [window, document.documentElement];
const element = document.getElementById(elementId);
return [element, element];
}
export function removeScroll(elementId?: string) {
const lookupKey = elementId || "@@window@@";
if (!(lookupKey in ScrollLookup.active)) return;
const [listenerTarget, element] = getScrollElementAndTarget(elementId);
if (listenerTarget) listenerTarget.removeEventListener("scroll", ScrollLookup.lookup[lookupKey].scrollFunction, false);
delete ScrollLookup.active[lookupKey];
delete ScrollLookup.lookup[lookupKey];
}
export function scrollElementIntoView(elementId: string, timeout?: number) {
if (timeout) {
setTimeout(() => scrollElementIntoView(elementId), timeout);
return;
}
const element = document.getElementById(elementId);
if (!element) console.warn("scrollElementIntoView elementId not found");
else element.scrollIntoView({ behavior: "smooth", block: "start" });
}