From 15dbfad97cd04cae74b09f6f35015b1d92f4082c Mon Sep 17 00:00:00 2001 From: xiefeng <1294699027@qq.com> Date: Mon, 11 Dec 2023 22:53:39 +0800 Subject: [PATCH] fix(affix): fix affix scroll out of container with target is non-window --- docs/src/app/globals.scss | 3 +- docs/src/content/demos/affix/with-target.tsx | 2 +- docs/src/content/docs/components/affix.mdx | 13 ++--- .../_utils/hooks/useNormalizedContainer.ts | 3 +- .../src/components/affix/Affix.tsx | 51 ++++++++++++++++--- 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/docs/src/app/globals.scss b/docs/src/app/globals.scss index 3fdff6ea..f7348261 100644 --- a/docs/src/app/globals.scss +++ b/docs/src/app/globals.scss @@ -68,7 +68,8 @@ a { td { max-width: 240px; - padding: 10px 16px; + padding: 8px 16px; + line-height: 2; text-align: left; border-top: 1px solid var(--yike-line-color-1); transition: border 0.3s; diff --git a/docs/src/content/demos/affix/with-target.tsx b/docs/src/content/demos/affix/with-target.tsx index 7b511fb9..55ff457f 100644 --- a/docs/src/content/demos/affix/with-target.tsx +++ b/docs/src/content/demos/affix/with-target.tsx @@ -16,7 +16,7 @@ export default () => { }} >
- targetRef.current}> + targetRef.current} targetContainer={() => window}>
diff --git a/docs/src/content/docs/components/affix.mdx b/docs/src/content/docs/components/affix.mdx index 6befae85..006d11ee 100644 --- a/docs/src/content/docs/components/affix.mdx +++ b/docs/src/content/docs/components/affix.mdx @@ -29,9 +29,10 @@ order: 2 ## API -| 参数 | 描述 | 类型 | 默认值 | -| -------- | ------------------ | ----------------------------- | -------------- | -| offset | 偏移量 | `number` | 0 | -| position | 偏移方向 | `'top' \| 'bottom'` | `'top'` | -| target | 滚动容器 | `() => HTMLElement` | `() => window` | -| onChange | 固定状态改变时触发 | `(affixed?: boolean) => void` | - | +| 参数 | 描述 | 类型 | 默认值 | +|-----------------|-----------------------------------------------------------------|-------------------------------|----------------| +| offset | 偏移量 | `number` | 0 | +| position | 偏移方向 | `'top' \| 'bottom'` | `'top'` | +| target | 滚动容器 | `() => HTMLElement \| Window` | `() => window` | +| targetContainer | `target` 的外层滚动元素,用于解决 `target` 为非 `window` 时,外层元素滚动会导致固钉跑出容器问题。 | `() => HTMLElement \| Window` | - | +| onChange | 固定状态改变时触发 | `(affixed?: boolean) => void` | - | diff --git a/packages/yike-design/src/components/_utils/hooks/useNormalizedContainer.ts b/packages/yike-design/src/components/_utils/hooks/useNormalizedContainer.ts index 7339874a..d79d8315 100644 --- a/packages/yike-design/src/components/_utils/hooks/useNormalizedContainer.ts +++ b/packages/yike-design/src/components/_utils/hooks/useNormalizedContainer.ts @@ -9,6 +9,5 @@ export const useNormalizedContainer = (container: ComponentContainer) => { }); return () => clearTimeout(timer); }, [container]); - // eslint-disable-next-line react-hooks/exhaustive-deps - return React.useCallback(() => normalizedContainer, [container, normalizedContainer]); + return React.useCallback(() => normalizedContainer, [normalizedContainer]); }; diff --git a/packages/yike-design/src/components/affix/Affix.tsx b/packages/yike-design/src/components/affix/Affix.tsx index f60305f4..48c4bf5e 100644 --- a/packages/yike-design/src/components/affix/Affix.tsx +++ b/packages/yike-design/src/components/affix/Affix.tsx @@ -16,6 +16,7 @@ interface AffixProps { offset?: number; position?: 'top' | 'bottom'; target?: ComponentContainer; + targetContainer?: ComponentContainer; onChange?: (affixed: boolean) => void; children: React.ReactNode; } @@ -25,10 +26,12 @@ interface AffixRef { } const Affix = React.forwardRef((props, ref) => { - const { offset = 0, position = 'top', target = getDefaultContainer, onChange } = props; + const { offset = 0, position = 'top', target = getDefaultContainer, onChange, targetContainer = target } = props; const getTargetFunc = useNormalizedContainer(target); + const getTargetContainerFunc = useNormalizedContainer(targetContainer); + const fixedNodeRef = React.useRef(null); const affixPlaceholderRef = React.useRef(null); @@ -37,15 +40,25 @@ const Affix = React.forwardRef((props, ref) => { const measurePosition = React.useCallback(() => { const target = getTargetFunc(); - if (!affixPlaceholderRef.current || !target) { + if (!affixPlaceholderRef.current || !target || !fixedNodeRef.current) { return; } + const fixedNodeRect = fixedNodeRef.current.getBoundingClientRect(); const placeholderRect = affixPlaceholderRef.current.getBoundingClientRect(); - const { top: currentScreenTop } = placeholderRect; - const currentScreenBottom = window.innerHeight - placeholderRect.bottom; + const placeholderScreenTop = placeholderRect.top; + const placeholderScreenBottom = window.innerHeight - placeholderRect.bottom; + + const fixedNodeScreenTop = fixedNodeRect.top; + const fixedNodeScreenBottom = window.innerHeight - fixedNodeRect.bottom; + if (position === 'top') { const fixedTop = getFixedTop(target, offset); - if (currentScreenTop <= fixedTop && !affixStyle) { + if (affixStyle && fixedNodeScreenTop !== fixedTop) { + setAffixStyle({ + ...affixStyle, + top: fixedTop, + }); + } else if (placeholderScreenTop <= fixedTop && !affixStyle) { setAffixStyle({ position: 'fixed', top: fixedTop, @@ -55,14 +68,19 @@ const Affix = React.forwardRef((props, ref) => { height: fixedNodeRef.current?.offsetHeight, }); onChange?.(true); - } else if (currentScreenTop > fixedTop && affixStyle) { + } else if (placeholderScreenTop > fixedTop && affixStyle) { setAffixStyle(undefined); setPlaceholderStyle(undefined); onChange?.(false); } } else { const fixedBottom = getFixedBottom(target, offset); - if (currentScreenBottom <= fixedBottom && !affixStyle) { + if (affixStyle && fixedNodeScreenBottom !== fixedBottom) { + setAffixStyle({ + ...affixStyle, + bottom: fixedBottom, + }); + } else if (placeholderScreenBottom <= fixedBottom && !affixStyle) { setAffixStyle({ position: 'fixed', bottom: fixedBottom, @@ -72,7 +90,7 @@ const Affix = React.forwardRef((props, ref) => { height: fixedNodeRef.current?.offsetHeight, }); onChange?.(true); - } else if (currentScreenBottom > fixedBottom && affixStyle) { + } else if (placeholderScreenBottom > fixedBottom && affixStyle) { setAffixStyle(undefined); setPlaceholderStyle(undefined); onChange?.(false); @@ -100,6 +118,23 @@ const Affix = React.forwardRef((props, ref) => { }; }, [getTargetFunc, updatePosition]); + React.useEffect(() => { + const target = getTargetFunc(); + const targetContainer = getTargetContainerFunc(); + if (!target || !targetContainer || target === targetContainer) { + return; + } + TRIGGER_EVENTS.forEach(event => { + targetContainer.addEventListener(event, updatePosition); + }); + return () => { + TRIGGER_EVENTS.forEach(event => { + targetContainer.removeEventListener(event, updatePosition); + }); + updatePosition.cancel(); + }; + }, [getTargetFunc, getTargetContainerFunc, updatePosition]); + React.useEffect(() => { measurePosition(); }, [measurePosition]);