From ebeb69c8d471baa9fd723523e1d52c25fef840e4 Mon Sep 17 00:00:00 2001 From: jasonelchen Date: Mon, 17 Jan 2022 17:07:32 +0800 Subject: [PATCH] =?UTF-8?q?fix(affix):=20=E4=BF=AE=E5=A4=8D=20Affix=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=20zIndex=20=E5=8F=82=E6=95=B0=E6=97=A0?= =?UTF-8?q?=E6=95=88=E5=92=8C=20offsetTop=20=E4=B8=BA=200=20=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E5=9B=BA=E5=AE=9A=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/affix/Affix.tsx | 60 ++++++++++++++++++++++++++++++--------------- src/affix/affix.md | 6 ++--- src/affix/index.ts | 2 +- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/affix/Affix.tsx b/src/affix/Affix.tsx index 9b5136a66..9dfcc2365 100644 --- a/src/affix/Affix.tsx +++ b/src/affix/Affix.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; import isFunction from 'lodash/isFunction'; -import { StyledProps , ScrollContainerElement } from '../common'; +import { StyledProps, ScrollContainerElement } from '../common'; import { TdAffixProps } from './type'; import { getScrollContainer } from '../_util/dom'; import useConfig from '../_util/useConfig'; @@ -15,19 +15,19 @@ interface StateRef { scrollContainer?: ScrollContainerElement; } -interface RefProps { +export interface AffixRef { calcInitValue: () => void; handleScroll: () => void; } -const Affix = forwardRef((props, ref) => { - const { children, container = () => window, offsetBottom, offsetTop, onFixedChange } = props; +const Affix = forwardRef((props, ref) => { + const { children, zIndex, container = () => window, offsetBottom, offsetTop, onFixedChange } = props; const [affixed, setAffixed] = useState(false); const { classPrefix } = useConfig(); - const affixRef = useRef(); - const affixWrapRef = useRef(); + const affixRef = useRef(null); + const affixWrapRef = useRef(null); const stateRef = useRef({ ticking: false, oldWidth: 0, oldHeight: 0, containerHeight: 0 }); const handleScroll = useCallback(() => { @@ -35,14 +35,20 @@ const Affix = forwardRef((props, ref) => { if (!ticking) { window.requestAnimationFrame(() => { const affixEl = affixRef.current; - const { top } = affixWrapRef.current.getBoundingClientRect(); // top = 节点到页面顶部的距离,包含 scroll 中的高度 - let containerTop = 0; // containerTop = 容器到页面顶部的距离 + + // top = 节点到页面顶部的距离,包含 scroll 中的高度 + const top = affixWrapRef.current?.getBoundingClientRect()?.top ?? 0; + + // containerTop = 容器到页面顶部的距离 + let containerTop = 0; if (scrollContainer instanceof HTMLElement) { containerTop = scrollContainer.getBoundingClientRect().top; } + let fixedTop: number | false; const calcTop = top - containerTop; // 节点顶部到 container 顶部的距离 - const calcBottom = containerTop + containerHeight - offsetBottom; // 计算 bottom 相对应的 top 值 + const calcBottom = containerTop + containerHeight - (offsetBottom ?? 0); // 计算 bottom 相对应的 top 值 + if (offsetTop !== undefined && calcTop <= offsetTop) { // top 的触发 fixedTop = containerTop + offsetTop; @@ -53,21 +59,34 @@ const Affix = forwardRef((props, ref) => { fixedTop = false; } - if (fixedTop !== false) { - affixEl.className = `${classPrefix}-affix`; - affixEl.style.top = `${fixedTop}px`; - affixEl.style.width = `${oldWidth}px`; - } else { - affixEl.removeAttribute('class'); - affixEl.removeAttribute('style'); + if (affixEl) { + if (fixedTop !== false) { + affixEl.className = `${classPrefix}-affix`; + affixEl.style.top = `${fixedTop}px`; + affixEl.style.width = `${oldWidth}px`; + + if (zIndex) { + affixEl.style.zIndex = `${zIndex}`; + } + } else { + affixEl.removeAttribute('class'); + affixEl.removeAttribute('style'); + } } - setAffixed(!!fixedTop); - if (isFunction(onFixedChange)) onFixedChange(!!fixedTop, { top: fixedTop }); + + const affixed = fixedTop !== false; + + setAffixed(affixed); + + if (isFunction(onFixedChange)) { + onFixedChange(affixed, { top: +fixedTop }); + } + stateRef.current.ticking = false; }); } stateRef.current.ticking = true; - }, [classPrefix, offsetBottom, offsetTop, onFixedChange]); + }, [classPrefix, offsetBottom, offsetTop, zIndex, onFixedChange]); const calcInitValue = useCallback(() => { const scrollContainer = getScrollContainer(container); @@ -102,8 +121,9 @@ const Affix = forwardRef((props, ref) => { if (stateRef.current.scrollContainer) { stateRef.current.scrollContainer.addEventListener('scroll', handleScroll); window.addEventListener('resize', handleScroll); + return () => { - stateRef.current.scrollContainer.removeEventListener('scroll', handleScroll); + stateRef.current.scrollContainer?.removeEventListener('scroll', handleScroll); window.removeEventListener('resize', handleScroll); }; } diff --git a/src/affix/affix.md b/src/affix/affix.md index 271f98936..2f77a0742 100644 --- a/src/affix/affix.md +++ b/src/affix/affix.md @@ -5,8 +5,8 @@ 名称 | 类型 | 默认值 | 说明 | 必传 -- | -- | -- | -- | -- -container | String / Function | () => (() => window) | 指定滚动的容器。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body。TS 类型:`ScrollContainer` | N -offsetBottom | Number | 0 | 距离容器顶部达到指定距离后触发固定 | N -offsetTop | Number | 0 | 距离容器底部达到指定距离后触发固定 | N +container | String / Function | () => window | 指定滚动的容器。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body。TS 类型:`ScrollContainer` | N +offsetBottom | Number | 0 | 距离容器底部达到指定距离后触发固定 | N +offsetTop | Number | 0 | 距离容器顶部达到指定距离后触发固定 | N zIndex | Number | - | 固钉定位层级,样式默认为 500 | N onFixedChange | Function | | TS 类型:`(affixed: boolean, context: { top: number }) => void`
固定状态发生变化时触发 | N diff --git a/src/affix/index.ts b/src/affix/index.ts index 02c831b67..a1a40e1aa 100644 --- a/src/affix/index.ts +++ b/src/affix/index.ts @@ -4,7 +4,7 @@ import './style/index.js'; export { Affix }; -export type { AffixProps } from './Affix'; +export type { AffixProps, AffixRef } from './Affix'; export * from './type'; export default Affix;