-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathindex.js
148 lines (130 loc) · 4.32 KB
/
index.js
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { useEffect, useRef, useState, useCallback } from 'react';
// Edge has a bug where scrollHeight is 1px bigger than clientHeight when there's no scroll.
const isEdge = typeof navigator !== 'undefined' && /Edge\/\d./i.test(window.navigator.userAgent);
// Small hook to use ResizeOberver if available. This fixes some issues when the component is resized.
// This needs a polyfill to work on all browsers. The polyfill is not included in order to keep the package light.
function useResizeObserver(ref, callback) {
useEffect(() => {
if (typeof window !== 'undefined' && window.ResizeObserver) {
const resizeObserver = new ResizeObserver((entries) => {
// Wrap it in requestAnimationFrame to avoid this error - ResizeObserver loop limit exceeded
window.requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) {
return;
}
callback(entries[0].contentRect);
});
});
resizeObserver.observe(ref.current);
return () => {
resizeObserver.disconnect();
};
}
}, [ref]);
};
function throttle(func, wait) {
let context, args, result;
let timeout = null;
let previous = 0;
const later = function () {
timeout = null;
result = func.apply(context, args);
if (!timeout) {
context = args = null;
}
};
return function () {
const now = Date.now();
const remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) {
context = args = null;
}
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
return result;
};
}
function useScrollInfo(throttleTime = 50) {
const [scroll, setScroll] = useState({ x: {}, y: {} });
const ref = useRef(null);
const previousScroll = useRef(null);
useResizeObserver(ref, () => {
update();
});
function update() {
const element = ref.current;
let maxY = element.scrollHeight - element.clientHeight;
const maxX = element.scrollWidth - element.clientWidth;
// Edge has a bug where scrollHeight is 1px bigger than clientHeight when there's no scroll.
if (isEdge && maxY === 1 && element.scrollTop === 0) {
maxY = 0;
}
const percentageY = maxY !== 0 ? element.scrollTop / maxY : null;
const percentageX = maxX !== 0 ? element.scrollLeft / maxX : null;
let classNameY = 'no-scroll-y';
if (percentageY === 0) {
classNameY = 'scroll-top';
} else if (percentageY === 1) {
classNameY = 'scroll-bottom';
} else if (percentageY) {
classNameY = 'scroll-middle-y';
}
let classNameX = 'no-scroll-x';
if (percentageX === 0) {
classNameX = 'scroll-left';
} else if (percentageX === 1) {
classNameX = 'scroll-right';
} else if (percentageX) {
classNameX = 'scroll-middle-x';
}
const previous = previousScroll.current;
const scrollInfo = {
x: {
percentage: percentageX,
value: element.scrollLeft,
total: maxX,
className: classNameX,
direction: previous ? Math.sign(element.scrollLeft - previous.x.value) : 0,
},
y: {
percentage: percentageY,
value: element.scrollTop,
total: maxY,
className: classNameY,
direction: previous ? Math.sign(element.scrollTop - previous.y.value) : 0,
}
};
previousScroll.current = scrollInfo;
setScroll(scrollInfo);
}
const throttledUpdate = throttle(update, throttleTime);
const setRef = useCallback(node => {
if (node) {
// When the ref is first set (after mounting)
node.addEventListener('scroll', throttledUpdate);
if (!window.ResizeObserver) {
window.addEventListener('resize', throttledUpdate); // Fallback if ResizeObserver is not available
}
ref.current = node;
throttledUpdate(); // initialization
} else if (ref.current) {
// When unmounting
ref.current.removeEventListener('scroll', throttledUpdate);
if (!window.ResizeObserver) {
window.removeEventListener('resize', throttledUpdate);
}
}
}, []);
return [scroll, setRef, ref];
}
export default useScrollInfo;