Skip to content

Commit e350775

Browse files
committed
fix(Table): improve horizontal scroll handling for fixedRows and affixedHeader
1 parent f2c04f2 commit e350775

File tree

1 file changed

+60
-15
lines changed

1 file changed

+60
-15
lines changed

packages/components/table/hooks/useAffix.ts

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { useState, useRef, useMemo, useEffect } from 'react';
2-
import { TdBaseTableProps } from '../type';
3-
import { AffixProps } from '../../affix';
1+
import { useEffect, useMemo, useRef, useState } from 'react';
42
import { off, on } from '../../_util/listener';
3+
import type { AffixProps } from '../../affix';
4+
import type { TdBaseTableProps } from '../type';
55

66
/**
77
* 1. 表头吸顶(普通表头吸顶 和 虚拟滚动表头吸顶)
@@ -11,6 +11,9 @@ import { off, on } from '../../_util/listener';
1111
*/
1212
export default function useAffix(props: TdBaseTableProps, { showElement }: { showElement: boolean }) {
1313
const tableContentRef = useRef<HTMLDivElement>(null);
14+
const lastTableScrollLeftRef = useRef<number>(0);
15+
const lastPageScrollLeftRef = useRef<number>(0);
16+
1417
// 吸顶表头
1518
const affixHeaderRef = useRef<HTMLDivElement>(null);
1619
// 吸底表尾
@@ -19,6 +22,7 @@ export default function useAffix(props: TdBaseTableProps, { showElement }: { sho
1922
const horizontalScrollbarRef = useRef<HTMLDivElement>(null);
2023
// 吸底分页器
2124
const paginationRef = useRef<HTMLDivElement>(null);
25+
2226
// 当表格完全滚动消失在视野时,需要隐藏吸顶表头
2327
const [showAffixHeader, setShowAffixHeader] = useState(true);
2428
// 当表格完全滚动消失在视野时,需要隐藏吸底尾部
@@ -32,23 +36,23 @@ export default function useAffix(props: TdBaseTableProps, { showElement }: { sho
3236
);
3337

3438
const isAffixed = useMemo(
35-
() => !!(props.headerAffixedTop || props.footerAffixedBottom || props.horizontalScrollAffixedBottom),
36-
[props.footerAffixedBottom, props.headerAffixedTop, props.horizontalScrollAffixedBottom],
39+
() =>
40+
!!(isVirtualScroll || props.headerAffixedTop || props.footerAffixedBottom || props.horizontalScrollAffixedBottom),
41+
[isVirtualScroll, props.footerAffixedBottom, props.headerAffixedTop, props.horizontalScrollAffixedBottom],
3742
);
3843

39-
let lastScrollLeft = 0;
4044
const onHorizontalScroll = (scrollElement?: HTMLElement) => {
41-
if (!isAffixed && !isVirtualScroll) return;
45+
if (!isAffixed) return;
4246
let target = scrollElement;
4347
if (!target && tableContentRef.current) {
44-
lastScrollLeft = 0;
48+
lastTableScrollLeftRef.current = 0;
4549
target = tableContentRef.current;
4650
}
4751
if (!target) return;
4852
const left = target.scrollLeft;
4953
// 如果 lastScrollLeft 等于 left,说明不是横向滚动,不需要更新横向滚动距离
50-
if (lastScrollLeft === left) return;
51-
lastScrollLeft = left;
54+
if (lastTableScrollLeftRef.current === left) return;
55+
lastTableScrollLeftRef.current = left;
5256
// 表格内容、吸顶表头、吸底表尾、吸底横向滚动更新
5357
const toUpdateScrollElement = [
5458
tableContentRef.current,
@@ -63,6 +67,30 @@ export default function useAffix(props: TdBaseTableProps, { showElement }: { sho
6367
}
6468
};
6569

70+
const onPageHorizontalScroll = () => {
71+
if (!isAffixed) return;
72+
if (typeof window === 'undefined') return;
73+
74+
const pageScrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
75+
76+
if (lastPageScrollLeftRef.current === pageScrollLeft) return;
77+
lastPageScrollLeftRef.current = pageScrollLeft;
78+
79+
const toUpdateScrollElement = [affixHeaderRef.current, affixFooterRef.current, horizontalScrollbarRef.current];
80+
81+
toUpdateScrollElement.forEach((el) => {
82+
if (!el) return;
83+
// fixedRows 使用 sticky,吸顶表头使用 absolute,需要考虑页面滚动对定位的影响
84+
if (props.fixedRows && props.fixedRows.length > 0) {
85+
// eslint-disable-next-line no-param-reassign
86+
el.style.marginLeft = `-${pageScrollLeft}px`;
87+
} else if (el.style.marginLeft) {
88+
// eslint-disable-next-line no-param-reassign
89+
el.style.marginLeft = '';
90+
}
91+
});
92+
};
93+
6694
// 吸底的元素(footer、横向滚动条、分页器)是否显示
6795
const isAffixedBottomElementShow = (elementRect: DOMRect, tableRect: DOMRect, headerHeight: number) =>
6896
tableRect.top + headerHeight < elementRect.top && elementRect.top > elementRect.height;
@@ -73,7 +101,7 @@ export default function useAffix(props: TdBaseTableProps, { showElement }: { sho
73101
};
74102

75103
const updateAffixHeaderOrFooter = () => {
76-
if (!isAffixed && !isVirtualScroll) return;
104+
if (!isAffixed) return;
77105
const pos = tableContentRef.current?.getBoundingClientRect();
78106
if (!pos) return;
79107
const headerRect = tableContentRef.current?.querySelector('thead')?.getBoundingClientRect();
@@ -107,6 +135,7 @@ export default function useAffix(props: TdBaseTableProps, { showElement }: { sho
107135

108136
const onDocumentScroll = () => {
109137
updateAffixHeaderOrFooter();
138+
onPageHorizontalScroll();
110139
};
111140

112141
const onFootScroll = () => {
@@ -173,7 +202,7 @@ export default function useAffix(props: TdBaseTableProps, { showElement }: { sho
173202
on(horizontalScrollbarRef.current, 'mouseleave', onScrollbarMouseLeave);
174203
}
175204

176-
if ((isAffixed || isVirtualScroll) && tableContentRef.current) {
205+
if (isAffixed && tableContentRef.current) {
177206
on(tableContentRef.current, 'mouseenter', onTableContentMouseEnter);
178207
on(tableContentRef.current, 'mouseleave', onTableContentMouseLeave);
179208
}
@@ -211,17 +240,30 @@ export default function useAffix(props: TdBaseTableProps, { showElement }: { sho
211240
});
212241
};
213242

243+
const addPageHorizontalScrollListener = () => {
244+
if (typeof window === 'undefined') return;
245+
if (props.fixedRows && props.fixedRows.length > 0 && isAffixed) {
246+
onPageHorizontalScroll(); // initial sync
247+
on(window, 'scroll', onPageHorizontalScroll);
248+
}
249+
};
250+
214251
useEffect(() => {
215252
const timer = setTimeout(() => {
216253
addHorizontalScrollListeners();
254+
addPageHorizontalScrollListener();
217255
onHorizontalScroll();
218256
updateAffixHeaderOrFooter();
219257
clearTimeout(timer);
220258
});
221-
222-
return removeHorizontalScrollListeners;
259+
return () => {
260+
removeHorizontalScrollListeners();
261+
if (typeof window !== 'undefined') {
262+
off(window, 'scroll', onPageHorizontalScroll);
263+
}
264+
};
223265
// eslint-disable-next-line react-hooks/exhaustive-deps
224-
}, [affixHeaderRef, affixFooterRef, horizontalScrollbarRef, tableContentRef, showElement]);
266+
}, [affixHeaderRef, affixFooterRef, horizontalScrollbarRef, tableContentRef, showElement, props.fixedRows]);
225267

226268
useEffect(() => {
227269
addVerticalScrollListener();
@@ -234,6 +276,7 @@ export default function useAffix(props: TdBaseTableProps, { showElement }: { sho
234276
useEffect(() => {
235277
addHorizontalScrollListeners();
236278
onHorizontalScroll();
279+
addPageHorizontalScrollListener();
237280
// eslint-disable-next-line react-hooks/exhaustive-deps
238281
}, [
239282
props.data,
@@ -242,11 +285,13 @@ export default function useAffix(props: TdBaseTableProps, { showElement }: { sho
242285
props.footerAffixedBottom,
243286
props.horizontalScrollAffixedBottom,
244287
props.lazyLoad,
288+
props.fixedRows,
245289
]);
246290

247291
const setTableContentRef = (tableContent: HTMLDivElement) => {
248292
tableContentRef.current = tableContent;
249293
addVerticalScrollListener();
294+
addPageHorizontalScrollListener();
250295
};
251296

252297
return {

0 commit comments

Comments
 (0)