Skip to content

Commit 150fd1d

Browse files
committed
use IntersectionObserver for tracking progress
this removes the scroll listener and enables progression tracking via IntersectionObserver
1 parent 32fe67b commit 150fd1d

File tree

3 files changed

+59
-27
lines changed

3 files changed

+59
-27
lines changed

src/Scrollama.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1-
import React from 'react';
1+
import React, { useMemo, useState } from 'react';
22
import DebugOffset from './DebugOffset';
33
import { isOffsetInPixels } from './utils';
44

5+
const createThreshold = (theta, height) => {
6+
const count = Math.ceil(height / theta);
7+
const t = [];
8+
const ratio = 1 / count;
9+
for (let i = 0; i <= count; i += 1) {
10+
t.push(i * ratio);
11+
}
12+
return t;
13+
};
14+
515
const TinyScrollama = props => {
616
const {
717
debug,
@@ -10,10 +20,12 @@ const TinyScrollama = props => {
1020
onStepEnter,
1121
onStepExit,
1222
onStepProgress,
23+
threshold,
1324
} = props;
1425
const isOffsetDefinedInPixels = isOffsetInPixels(offset)
15-
const [lastScrollTop, setLastScrollTop] = React.useState(0);
16-
const [windowInnerHeight, setWindowInnerHeight] = React.useState(null);
26+
const [lastScrollTop, setLastScrollTop] = useState(0);
27+
const [windowInnerHeight, setWindowInnerHeight] = useState(null);
28+
1729
const handleSetLastScrollTop = (scrollTop) => {
1830
setLastScrollTop(scrollTop);
1931
};
@@ -31,11 +43,14 @@ const TinyScrollama = props => {
3143
}
3244
}, []);
3345

46+
const innerHeight = (windowInnerHeight || window.innerHeight)
3447

3548
const offsetValue = isOffsetDefinedInPixels
36-
? (+offset.replace('px', '') / (windowInnerHeight || window.innerHeight))
49+
? (+offset.replace('px', '') / innerHeight)
3750
: offset;
3851

52+
const progressThreshold = useMemo(() => createThreshold(threshold, innerHeight), [innerHeight]);
53+
3954
return (
4055
<React.Fragment>
4156
{debug && <DebugOffset offset={offset} />}
@@ -48,6 +63,7 @@ const TinyScrollama = props => {
4863
onStepProgress,
4964
lastScrollTop,
5065
handleSetLastScrollTop,
66+
progressThreshold,
5167
});
5268
})}
5369
</React.Fragment>
@@ -58,6 +74,7 @@ TinyScrollama.defaultProps = {
5874
onStepProgress: null,
5975
onStepEnter: () => {},
6076
onStepExit: () => {},
77+
threshold: 4,
6178
};
6279

6380
export default TinyScrollama;

src/Step.js

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ const useRootMargin = offset => {
55
return `-${offset * 100}% 0px -${100 - offset * 100}% 0px`;
66
}
77

8+
const useProgressRootMargin = (direction, offset) => {
9+
if (direction === 'down') return `${offset * 100}% 0px -${100 - offset * 100}% 0px`
10+
return `-${offset * 100}% 0px ${offset * 100}% 0px`;
11+
}
12+
813
const Step = props => {
914
const {
1015
children,
@@ -16,31 +21,48 @@ const Step = props => {
1621
onStepProgress,
1722
offset,
1823
scrollamaId,
24+
progressThreshold,
1925
} = props;
26+
27+
const scrollTop = document.documentElement.scrollTop;
28+
const direction = lastScrollTop < scrollTop ? 'down' : 'up';
29+
30+
const progressRootMargin = useProgressRootMargin(direction, offset);
2031
const rootMargin = useRootMargin(offset);
21-
const [ref, entry] = useIntersectionObserver({
32+
33+
const [node, setNode] = React.useState(null);
34+
const [isIntersecting, setIsIntersecting] = React.useState(false);
35+
36+
const [entry] = useIntersectionObserver({
2237
rootMargin,
2338
threshold: 0,
39+
nodeRef: node,
40+
});
41+
42+
const [scrollProgressEntry] = useIntersectionObserver({
43+
rootMargin: progressRootMargin,
44+
threshold: progressThreshold,
45+
nodeRef: node,
2446
});
25-
const [isIntersecting, setIsIntersecting] = React.useState(false);
2647

27-
const handleScroll = () => {
28-
const { height, top } = entry.target.getBoundingClientRect();
29-
const progress = Math.min(1, Math.max(0, (window.innerHeight * offset - top) / height));
3048

31-
onStepProgress &&
49+
React.useEffect(() => {
50+
if (isIntersecting) {
51+
const { height, top } = scrollProgressEntry.target.getBoundingClientRect();
52+
const progress = Math.min(1, Math.max(0, (window.innerHeight * offset - top) / height));
53+
54+
onStepProgress &&
3255
onStepProgress({
3356
progress,
3457
scrollamaId,
3558
data,
36-
element: entry.target,
37-
entry,
59+
element: scrollProgressEntry.target,
60+
entry: scrollProgressEntry,
3861
});
39-
};
62+
}
63+
}, [scrollProgressEntry]);
4064

4165
React.useEffect(() => {
42-
const scrollTop = document.documentElement.scrollTop;
43-
const direction = lastScrollTop < scrollTop ? 'down' : 'up';
4466
if (entry && !entry.isIntersecting && isIntersecting) {
4567
onStepExit({ element: entry.target, scrollamaId, data, entry, direction });
4668
setIsIntersecting(false);
@@ -50,17 +72,11 @@ const Step = props => {
5072
onStepEnter({ element: entry.target, scrollamaId, data, entry, direction});
5173
handleSetLastScrollTop(scrollTop)
5274
}
53-
if (entry && entry.isIntersecting && onStepProgress) {
54-
document.addEventListener('scroll', handleScroll);
55-
return () => {
56-
document.removeEventListener('scroll', handleScroll);
57-
};
58-
}
5975
}, [entry]);
6076

6177
return React.cloneElement(React.Children.only(children), {
6278
'data-react-scrollama-id': scrollamaId,
63-
ref,
79+
ref: setNode,
6480
entry,
6581
});
6682
};

src/useIntersectionObserver.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import { useEffect, useRef, useState } from 'react';
55
* https://medium.com/the-non-traditional-developer/how-to-use-an-intersectionobserver-in-a-react-hook-9fb061ac6cb5
66
*/
77

8-
export default ({ root = null, rootMargin, threshold = 0 }) => {
8+
export default ({ root = null, rootMargin, threshold = 0, nodeRef }) => {
99
const [entry, updateEntry] = useState({});
10-
const [node, setNode] = useState(null);
1110
const observer = useRef(null);
1211
useEffect(
1312
() => {
@@ -23,11 +22,11 @@ export default ({ root = null, rootMargin, threshold = 0 }) => {
2322

2423
const { current: currentObserver } = observer;
2524

26-
if (node) currentObserver.observe(node);
25+
if (nodeRef) currentObserver.observe(nodeRef);
2726

2827
return () => currentObserver.disconnect();
2928
},
30-
[node, root, rootMargin, threshold]
29+
[nodeRef, root, rootMargin, threshold]
3130
);
32-
return [setNode, entry];
31+
return [entry];
3332
};

0 commit comments

Comments
 (0)