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]);