import React, { useRef, useState, useEffect, useMemo, forwardRef, useImperativeHandle, useCallback, } from "react"; import { AffixProps } from "./AffixProps"; import { getTarget, getRect, throttle, getFixed } from "./util"; import { addListener, removeListener } from "./AffixManager"; import { uuid } from "../_util/uuid"; import { useIsomorphicLayoutEffect } from "../_util/use-isomorphic-layout-effect"; import { useUnmounted } from "../_util/use-unmounted"; export interface AffixHandles { update: () => void; } export const Affix = forwardRef(function Affix( { offsetTop, offsetBottom, target = window, children, style, className, }: AffixProps, ref: React.Ref ) { // 默认吸顶 if (typeof offsetTop === "undefined" && typeof offsetBottom === "undefined") { offsetTop = 0; // eslint-disable-line no-param-reassign } const affixRef = useRef(null); const placeholderRef = useRef(null); const [needUpdate, setNeedUpdate] = useState(false); const [affixStyle, setAffixStyle] = useState(undefined); const [placeholderStyle, setPlaceholderStyle] = useState( undefined ); const unmountedRef = useUnmounted(); const measure = useCallback(() => { const placeholder = placeholderRef.current; if (!needUpdate || !placeholder) { return; } const placeholderRect = getRect(placeholder); const sizeStyle = { width: placeholderRect.width, height: placeholderRect.height, }; setPlaceholderStyle(sizeStyle); if (typeof offsetTop !== "undefined") { const top = getFixed(target, placeholder, offsetTop); setAffixStyle( typeof top !== "undefined" ? Object.assign( { position: "fixed", zIndex: 10, top } as React.CSSProperties, sizeStyle ) : undefined ); } else { const bottom = getFixed(target, placeholder, offsetBottom, false); setAffixStyle( typeof bottom !== "undefined" ? Object.assign( { position: "fixed", zIndex: 10, bottom } as React.CSSProperties, sizeStyle ) : undefined ); } setNeedUpdate(false); }, [needUpdate, offsetBottom, offsetTop, target]); const update = useMemo( () => throttle(() => { if (!unmountedRef.current) { setAffixStyle(undefined); setPlaceholderStyle(undefined); setNeedUpdate(true); } }), // eslint-disable-next-line react-hooks/exhaustive-deps [] ); useIsomorphicLayoutEffect(() => { measure(); }, [measure, needUpdate]); useImperativeHandle(ref, () => ({ update, })); useEffect(() => { update(); const id = uuid(); const targetElement = getTarget(target); addListener(targetElement, id, update); return () => removeListener(targetElement, id); }, [offsetBottom, offsetTop, target, update]); return (
{affixStyle &&
}
{children}
); }); Affix.displayName = "Affix";