- 最近滚动祖先原则:
- 粘滞元素会相对于其DOM树中最近的滚动容器定位
- 如果没有设置滚动容器,则相对于视口定位
- 常见定位上下文:
- 具有
overflow
属性的容器(auto
,scroll
,hidden
,clip
) - 文档正文(
<body>
) - 视口本身(如果没有找到其他滚动容器)
- 具有
- 排除无效容器:
- 设置了
overflow: visible
的容器不会限制粘滞元素 - 设置了
position: static
的容器不会影响粘滞元素
- 设置了
DOM 不方便调整时,换种思路实现:
// sticky 模拟
const tableBodyDOM = document.querySelector(`#${TABLE_DOM_ID} .dpt-table-body`) as HTMLDivElement | null;
const headerDOM = document.querySelector(`#${HEADER_DOM_ID}`) as HTMLDivElement | null;
const divDOM = document.createElement('div');
const headerHeight = headerDOM?.getBoundingClientRect().height || 0;
const handleScroll = () => {
if (!tableBodyDOM || !headerDOM) {
return;
}
const scrollTop = tableBodyDOM.scrollTop;
if (scrollTop <= headerHeight) {
divDOM.style.marginBottom = `-${headerHeight - scrollTop}px`;
headerDOM.style.setProperty('--header-offset', `-${scrollTop}px`);
} else {
divDOM.style.marginBottom = `0`;
headerDOM.style.setProperty('--header-offset', `-${headerHeight}px`);
}
};
if (headerDOM && tableBodyDOM) {
const canScroll = tableBodyDOM.scrollHeight > Math.ceil(tableBodyDOM.clientHeight);
if (canScroll) {
divDOM.style.width = '100%';
divDOM.style.height = `${headerHeight}px`;
divDOM.style.marginBottom = `-${headerHeight}px`;
tableBodyDOM.insertBefore(divDOM, tableBodyDOM.firstChild);
tableBodyDOM.addEventListener('scroll', handleScroll);
} else {
headerDOM?.style.setProperty('--header-offset', `0`);
}
}
return () => {
if (divDOM) {
if (typeof divDOM.remove === 'function') {
divDOM.remove();
} else if (tableBodyDOM && divDOM.parentNode === tableBodyDOM) {
tableBodyDOM.removeChild(divDOM);
}
}
headerDOM?.style.setProperty('--header-offset', `0`);
tableBodyDOM?.removeEventListener('scroll', handleScroll);
};