Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
82 lines
3.9 KiB
Plaintext
82 lines
3.9 KiB
Plaintext
import React, { useCallback, useEffect, useReducer, useRef, } from 'react';
|
|
import { ScrollInfoContext } from '../ScrollInfoProvider/context.js';
|
|
const reducer = (state, payload) => {
|
|
const { timestamp, animationRef, } = payload;
|
|
animationRef.current = null;
|
|
const { x: prevScrollX, y: prevScrollY, xDirection: prevXDirection, yDirection: prevYDirection, eventsFired: prevEventsFired, hasScrolled: prevHasScrolled, } = state;
|
|
// Set currentScroll to zero on mount for cross-browser compatibility --
|
|
// Some browsers populate the cached window.pageOffset at different points of the component lifecycle.
|
|
// Chrome mounts with cached window.pageOffset coordinates even before firing the respective cached scroll event.
|
|
// Neither Safari and FireFox populate these coordinates until this cached scroll (natively triggered by the browser).
|
|
// The presence of a timestamp indicates that the caller of this method was not componentDidMount,
|
|
// but rather a true scroll event via requestAnimationFrame. Keep at zero otherwise.
|
|
const hasScrolled = prevHasScrolled || Boolean(timestamp);
|
|
const currentScrollX = hasScrolled ? window.pageXOffset : 0;
|
|
const currentScrollY = hasScrolled ? window.pageYOffset : 0;
|
|
// Only increment the eventsFired state after the first true scroll event.
|
|
const eventsFired = hasScrolled ? prevEventsFired + 1 : prevEventsFired;
|
|
const xDifference = currentScrollX - prevScrollX;
|
|
const yDifference = currentScrollY - prevScrollY;
|
|
const xPercentage = (currentScrollX / (document.body.scrollWidth - window.innerWidth)) * 100;
|
|
const yPercentage = (currentScrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
|
|
const totalPercentage = (xPercentage + yPercentage) / 2;
|
|
const xDirection = xDifference > 0 ? 'right' : xDifference < 0 ? 'left' : prevXDirection; // eslint-disable-line no-nested-ternary
|
|
const yDirection = yDifference > 0 ? 'down' : yDifference < 0 ? 'up' : prevYDirection; // eslint-disable-line no-nested-ternary
|
|
return {
|
|
x: currentScrollX,
|
|
y: currentScrollY,
|
|
xDifference,
|
|
yDifference,
|
|
xDirection,
|
|
yDirection,
|
|
xPercentage,
|
|
yPercentage,
|
|
totalPercentage,
|
|
eventsFired,
|
|
hasScrolled,
|
|
};
|
|
};
|
|
export const ScrollInfoProvider = (props) => {
|
|
const { children, } = props;
|
|
const animationRef = useRef(null);
|
|
const [state, dispatch] = useReducer(reducer, {
|
|
x: 0,
|
|
y: 0,
|
|
xDifference: 0,
|
|
yDifference: 0,
|
|
xDirection: undefined,
|
|
yDirection: undefined,
|
|
xPercentage: 0,
|
|
yPercentage: 0,
|
|
totalPercentage: 0,
|
|
eventsFired: 0,
|
|
hasScrolled: false,
|
|
});
|
|
const requestAnimation = useCallback((e) => {
|
|
if (animationRef.current) {
|
|
cancelAnimationFrame(animationRef.current);
|
|
}
|
|
animationRef.current = requestAnimationFrame((timestamp) => {
|
|
dispatch({
|
|
e,
|
|
timestamp,
|
|
animationRef,
|
|
});
|
|
});
|
|
}, []);
|
|
useEffect(() => {
|
|
window.addEventListener('scroll', requestAnimation);
|
|
return () => {
|
|
window.removeEventListener('scroll', requestAnimation);
|
|
};
|
|
}, [requestAnimation]);
|
|
// use this effect to test rAF debounce by requesting animation every 1ms, for a total 120ms
|
|
// count the number of times 'requestAnimation' callback is fired compared to the reducer
|
|
// results: ~23 requests will be made, ~17 requests will be canceled, so only ~8 will truly dispatch
|
|
// useEffect(() => {
|
|
// const firstID = setInterval(requestAnimation, 1);
|
|
// setInterval(() => clearInterval(firstID), 120);
|
|
// }, [requestAnimation]);
|
|
return (React.createElement(ScrollInfoContext.Provider, { value: Object.assign({}, state) }, children && children));
|
|
};
|
|
//# sourceMappingURL=index.js.map |