Skip to content

Commit

Permalink
fix(affix): 修复 Affix 组件 zIndex 参数无效和 offsetTop 为 0 无法固定的问题
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonelchen committed Jan 17, 2022
1 parent 0264675 commit ebeb69c
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 24 deletions.
60 changes: 40 additions & 20 deletions src/affix/Affix.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,34 +15,40 @@ interface StateRef {
scrollContainer?: ScrollContainerElement;
}

interface RefProps {
export interface AffixRef {
calcInitValue: () => void;
handleScroll: () => void;
}

const Affix = forwardRef<RefProps, AffixProps>((props, ref) => {
const { children, container = () => window, offsetBottom, offsetTop, onFixedChange } = props;
const Affix = forwardRef<AffixRef, AffixProps>((props, ref) => {
const { children, zIndex, container = () => window, offsetBottom, offsetTop, onFixedChange } = props;

const [affixed, setAffixed] = useState<boolean>(false);
const { classPrefix } = useConfig();

const affixRef = useRef<HTMLDivElement>();
const affixWrapRef = useRef<HTMLDivElement>();
const affixRef = useRef<HTMLDivElement>(null);
const affixWrapRef = useRef<HTMLDivElement>(null);
const stateRef = useRef<StateRef>({ ticking: false, oldWidth: 0, oldHeight: 0, containerHeight: 0 });

const handleScroll = useCallback(() => {
const { ticking, scrollContainer, containerHeight, oldWidth } = stateRef.current;
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;
Expand All @@ -53,21 +59,34 @@ const Affix = forwardRef<RefProps, AffixProps>((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);
Expand Down Expand Up @@ -102,8 +121,9 @@ const Affix = forwardRef<RefProps, AffixProps>((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);
};
}
Expand Down
6 changes: 3 additions & 3 deletions src/affix/affix.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`<br/>固定状态发生变化时触发 | N
2 changes: 1 addition & 1 deletion src/affix/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

0 comments on commit ebeb69c

Please sign in to comment.