jsx import { Interpolation , jsx , SerializedStyles , Theme } from "@emotion/react" ; import * as CSS from "csstype" ; import { useEffect , useLayoutEffect , useRef , useState } from "react" ; import { Indicator } from "../Indicator" ; import { RowCenter } from "../Flex/Row" ; import { style } from "./style" ; export interface ScrollEvent { containerHeight : number ; contentHeight : number ; scrollTop : number ; maxScroll : number ; direction : "upward" | "downward" ; rawEvent ? : React . UIEvent ; } export interface ScrollViewProps extends Omit < React . HTMLProps < HTMLDivElement > , "onScroll" > { children ? : React . ReactNode ; height ? : CSS . Property . Height ; reachTopThreshold ? : number ; onReachTop ? : ( event : ScrollEvent ) => void ; reachBottomThreshold ? : number ; onReachBottom ? : ( event : ScrollEvent ) => void ; showLoading ? : boolean ; loadingContent ? : React . ReactNode ; onScroll ? : ( event : ScrollEvent ) => void ; containerStyle ? : SerializedStyles ; wrapperStyle ? : SerializedStyles ; loadingStyle ? : SerializedStyles ; } export function ScrollView ( props ) { const { children , height , reachTopThreshold = 50 , onReachTop , reachBottomThreshold = 50 , onReachBottom , showLoading = true , loadingContent , onScroll , containerStyle , wrapperStyle , loadingStyle , ... attrs } = props ; const heightStyle = { } ; if ( height ) { heightStyle . height = height ; } const container = useRef < HTMLDivElement > ( null ) ; const child = useRef < HTMLDivElement > ( null ) ; const top = useRef < number > ( 0 ) ; const scrollCallback = ( rawEvent ) => { const box = container . current ! ; const scrollTop = box . scrollTop ; const contentHeight = box . scrollHeight ; const containerHeight = Math . min ( box . clientHeight , box . offsetHeight ) ; const loadingHeight = ( box . children . item ( 1 ) as HTMLElement ) ?. offsetHeight ?? 0 ; const maxScroll = contentHeight - containerHeight ; const event = { containerHeight , contentHeight , maxScroll , scrollTop , direction : scrollTop > top . current ? "downward" : "upward" , rawEvent , } ; onScroll ?. ( event ) ; if ( event . direction === "upward" && scrollTop < reachTopThreshold ) { onReachTop ?. ( event ) ; } if ( event . direction === "downward" && scrollTop > maxScroll - reachBottomThreshold - loadingHeight ) { onReachBottom ?. ( event ) ; } top . current = scrollTop ; } ; let showLoadingContent = null ; if ( showLoading ) { if ( ! loadingContent ) { showLoadingContent = ( < RowCenter css = { [ style . loading , loadingStyle ] } > < Indicator barColor = "#333" barCount = { 12 } / > < p > 数据加载中 ... < /p> < /RowCenter>); } else { showLoadingContent = loadingContent ; } } const [ hasScrollBar , setHasScrollBar ] = useState ( false ) ; useEffect ( ( ) => { let hasScrollBar = container . current ! . scrollHeight > container . current ! . clientHeight ; setHasScrollBar ( hasScrollBar ) ; } ) ; return ( < div css = { [ style . container , heightStyle , containerStyle ] } onScroll = { scrollCallback } ref = { container } { ... attrs } > < div css = { wrapperStyle } ref = { child } > { children } < /div> { hasScrollBar && showLoadingContent } < /div>); }