import React , { useCallback , useEffect , useRef , useState } from "react" ; import { ScrollContainerContext } from "./ScrollContext" ; import environment from "./utils/environment" ; interface IProps { snap : 'none' | 'proximity' | 'mandatory' ; children : React . ReactNodeArray ; scrollParent : Window | HTMLElement ; } interface IState { currentY : number ; viewportHeight : number ; totalPage : number ; totalHeight : number ; totalProgress : number ; realPage : number ; currentPage : number ; currentProgress : number ; } const ScrollAnimatorContainer = ( { snap = 'none' , children , scrollParent = window } ) => { const [ scrollData , setScrollData ] = useState < IState > ( { currentY : 0 , viewportHeight : 0 , totalPage : 0 , totalHeight : 0 , totalProgress : 0 , realPage : 0 , currentPage : 0 , currentProgress : 0 , } ) ; const doSnap = snap !== 'none' ; const scrollTimer = useRef < ReturnType < typeof setTimeout > > ( ) ; const scrollEvent = useCallback ( ( ) => { if ( snap && scrollTimer . current ) clearTimeout ( scrollTimer . current ) ; const currentY = scrollParent === window ? window . pageYOffset : ( scrollParent as HTMLElement ) . scrollTop ; const viewportHeight = scrollParent === window ? environment . height : ( scrollParent as HTMLElement ) . clientHeight ; const totalPage = children . length || 0 ; const totalHeight = totalPage * ( viewportHeight - 1 ) ; const totalProgress = currentY / totalHeight ; const realPage = currentY / viewportHeight ; const currentPage = Math . floor ( realPage ) ; const currentProgress = realPage - currentPage ; setScrollData ( { currentY , viewportHeight , totalPage , totalHeight , totalProgress , realPage , currentPage , currentProgress , } as IState ) ; if ( doSnap ) { scrollTimer . current = setTimeout ( ( ) => { const newCurrentPage = Math . round ( realPage ) ; let newCurrentY = currentY ; if ( snap === 'mandatory' || Math . abs ( newCurrentPage - realPage ) < 0.3 ) newCurrentY = newCurrentPage * viewportHeight ; if ( newCurrentY !== currentY ) window . scrollTo ( { top : newCurrentY , behavior : 'smooth' } ) ; } , 50 ) ; } } , [ children . length , doSnap , scrollParent , snap ] ) ; useEffect ( ( ) => { scrollEvent ( ) ; scrollParent . addEventListener ( "scroll" , scrollEvent ) ; scrollParent . addEventListener ( "resize" , scrollEvent ) ; return ( ) => scrollParent . removeEventListener ( "scroll" , scrollEvent ) ; } , [ scrollEvent , scrollParent ] ) ; return ( < div style = { { margin : 0 , padding : 0 , userSelect : "none" } } > < ScrollContainerContext . Provider value = { scrollData } > { children } < /ScrollContainerContext.Provider> < /div>); } ; export default ScrollAnimatorContainer ;