11import {
2- useState ,
3- useEffect ,
4- useRef ,
5- useContext ,
6- useMemo ,
72 type CSSProperties ,
3+ type FC ,
84 type KeyboardEvent ,
95 type ReactNode ,
10- type FC ,
6+ useContext ,
7+ useEffect ,
8+ useLayoutEffect ,
9+ useMemo ,
10+ useRef ,
11+ useState ,
12+ useTransition ,
1113} from "react" ;
1214import { motion , type MotionProps } from "framer-motion" ;
1315
@@ -25,6 +27,7 @@ export interface FPContainerInterface {
2527 onHide ?: Function ;
2628 onShow ?: Function ;
2729 outerStyle ?: CSSProperties ;
30+ scrollDebounceMs ?: number ;
2831 style ?: CSSProperties ;
2932 transitionTiming ?: number ;
3033}
@@ -38,12 +41,17 @@ export const FPContainer: FC<FPContainerInterface> = ({
3841 onHide,
3942 onShow,
4043 outerStyle = { } ,
44+ scrollDebounceMs = 125 ,
4145 style = { } ,
4246 transitionTiming = 700 ,
4347} ) => {
44- const throttled = useRef ( false ) ;
45- const ticking = useRef ( false ) ;
46- const scrollY = useRef ( isSsr ? 0 : window . scrollY ) ;
48+ const FPContainerInnerRef = useRef < HTMLDivElement > ( null ) ;
49+ const scrollTimer = useRef < Timer > ( null ) ;
50+ // @see https://developer.mozilla.org/en-US/docs/Web/API/Window/pageYOffset
51+ // ^ IE users dont deserve our support
52+ const scrollY = useRef < number > ( isSsr ? 0 : window . scrollY ) ;
53+ const throttled = useRef < boolean > ( false ) ;
54+ const ticking = useRef < boolean > ( false ) ;
4755
4856 const useOuterStyle = useMemo (
4957 ( ) => ( {
@@ -66,9 +74,9 @@ export const FPContainer: FC<FPContainerInterface> = ({
6674 [ style ]
6775 ) ;
6876
69- const FPContainerInnerRef = useRef < HTMLDivElement > ( null ) ;
77+ const { ReactFPRef , slides , isFullscreen } = useContext ( FPContext ) ;
7078
71- const { ReactFPRef , slides } = useContext ( FPContext ) ;
79+ const [ , startTransition ] = useTransition ( ) ;
7280
7381 const [ pageState , setPageState ] = useState ( {
7482 fullpageHeight : 0 ,
@@ -81,39 +89,49 @@ export const FPContainer: FC<FPContainerInterface> = ({
8189 } ) ;
8290
8391 // @see https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event
84- const handleScroll = ( e : UIEvent ) => {
92+ const handleScroll = ( ) => {
8593 if ( throttled . current || isSsr ) return ;
8694 throttled . current = true ;
8795
88- e . stopPropagation ( ) ;
89-
90- // scroll first, then enable throttling
91- setTimeout ( ( ) => {
92- throttled . current = false ;
93- } , transitionTiming ) ;
94-
9596 if ( ! ticking . current ) {
9697 window . requestAnimationFrame ( ( ) => {
9798 const newScrollY = window . scrollY ;
9899 const prevScrollY = scrollY . current ;
99100
100- if ( prevScrollY < newScrollY ) forward ( ) ;
101+ if ( newScrollY === 0 ) first ( ) ;
102+ else if (
103+ window . innerHeight + Math . round ( newScrollY ) >=
104+ document . body . offsetHeight
105+ )
106+ last ( ) ;
107+ else if ( prevScrollY < newScrollY ) forward ( ) ;
101108 else if ( prevScrollY > newScrollY ) back ( ) ;
102109
103110 if (
104111 pageState . resetScroll ||
105112 transitionTiming !== pageState . transitionTiming
106113 )
107- setPageState ( ( prevState ) => ( {
108- ...prevState ,
109- resetScroll : false ,
110- transitionTiming,
111- } ) ) ;
114+ startTransition ( ( ) => {
115+ setPageState ( ( prevState ) => ( {
116+ ...prevState ,
117+ resetScroll : false ,
118+ transitionTiming,
119+ } ) ) ;
120+ } ) ;
112121
113122 ticking . current = false ;
114123 } ) ;
115124 ticking . current = true ;
116125 }
126+
127+ setTimeout ( ( ) => {
128+ throttled . current = false ;
129+ } , transitionTiming ) ;
130+ } ;
131+
132+ const bouncedHandleScroll = ( ) => {
133+ clearTimeout ( scrollTimer . current ) ;
134+ scrollTimer . current = setTimeout ( ( ) => handleScroll ( ) , scrollDebounceMs ) ;
117135 } ;
118136
119137 const handleResize = ( ) => {
@@ -134,15 +152,17 @@ export const FPContainer: FC<FPContainerInterface> = ({
134152 if ( ! ticking . current ) {
135153 requestAnimationFrame ( ( ) => {
136154 const fullpageHeight = FPContainerInnerRef . current ! . clientHeight ;
137- // update count
138- setPageState ( ( prevState ) => ( {
139- ...prevState ,
140- fullpageHeight,
141- viewportHeight : Math . max (
142- document . documentElement . clientHeight ,
143- window . innerHeight
144- ) ,
145- } ) ) ;
155+
156+ startTransition ( ( ) => {
157+ setPageState ( ( prevState ) => ( {
158+ ...prevState ,
159+ fullpageHeight,
160+ viewportHeight : Math . max (
161+ document . documentElement . clientHeight ,
162+ window . innerHeight
163+ ) ,
164+ } ) ) ;
165+ } ) ;
146166 ReactFPRef . current ! . style . height = `${ fullpageHeight } px` ;
147167 ticking . current = false ;
148168 } ) ;
@@ -203,7 +223,10 @@ export const FPContainer: FC<FPContainerInterface> = ({
203223 slideIndex,
204224 translateY,
205225 } ;
206- setPageState ( ( prevState ) => ( { ...prevState , ...newPageState } ) ) ;
226+
227+ startTransition ( ( ) => {
228+ setPageState ( ( prevState ) => ( { ...prevState , ...newPageState } ) ) ;
229+ } ) ;
207230
208231 setTimeout ( ( ) => {
209232 throttled . current = false ;
@@ -256,20 +279,21 @@ export const FPContainer: FC<FPContainerInterface> = ({
256279 useEffect ( ( ) => {
257280 if ( isSsr ) return ;
258281
259- window . addEventListener ( "scroll" , handleScroll , { passive : true } ) ;
282+ window . addEventListener ( "scroll" , bouncedHandleScroll , { passive : true } ) ;
260283 window . addEventListener ( "resize" , handleResize , { passive : true } ) ;
261284 document . addEventListener ( "keydown" , handleKeys , { passive : true } ) ;
262285
263286 return ( ) => {
264- window . removeEventListener ( "scroll" , handleScroll ) ;
287+ window . removeEventListener ( "scroll" , bouncedHandleScroll ) ;
265288 window . removeEventListener ( "resize" , handleResize ) ;
266289 document . removeEventListener ( "keydown" , handleKeys ) ;
267290 } ;
268291 } ) ;
269292
270- useEffect ( ( ) => {
293+ useLayoutEffect ( ( ) => {
271294 handleResize ( ) ;
272- } ) ;
295+ handleScroll ( ) ;
296+ } , [ isFullscreen ] ) ;
273297
274298 return (
275299 < div style = { useOuterStyle } >
0 commit comments