跳到主要内容

sticky-header

demo

监听容器元素滚动触发的 scroll 事件

当滚动元素的scrollTopscrollLeft等属性>0时,表示内容区域发生了滚动,这时候可以为header追加box-shadow等属性,显示阴影效果,提升体验。

const StickyHeader: React.FC<Props> = ({
children
}) => {
const [scrollTop, setScrollTop] = useState(0);
const handleScroll: React.UIEventHandler<HTMLDivElement> = e => {
const { scrollTop } = e.target as HTMLDivElement;
setScrollTop(scrollTop);
};

return (
<div className='sticky-content-container' onScroll={handleScroll}>
<div
className={classNames('sticky-header', {
'bottom-shadow': scrollTop > 0
})}
></div>
<div className="scroll-content">
{children}
</div>
</div>
);
};

IntersectionObserver

  1. 参考how-to-detect-when-a-sticky-element-gets-pinned

做法有些 hack,利用-1px的偏移量,判断吸顶元素在IntersectionObserverEntry.intersectionRatio是否< 1,非常简单且通用性强,唯一的缺点是用在页面顶部的元素时,用户会感受到吸顶元素1px的位移。

注意设置threshold: [1],即当目标元素变得不完全可见时触发IntersectionObserver的回调函数。

<div className='sticky-content-container'>
<div className='sticky-header'></div>
<div className="scroll-content"></div>
</div>
.sticky-content-container {
width: 600px;
height: 300px;
overflow: auto;
border: 1px solid #f0f0f0;
background: #f0d4d4;
position: relative;
}

.sticky-header {
width: 100%;
height: 68px;
padding: 12px;
position: sticky;
top: 0;
background: #6b6767;
}

.bottom-shadow {
box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.75);
-webkit-box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.75);
transition: box-shadow 0.5s;
}

.scroll-content {
padding: 24px;
height: 800px;
}
useEffect(() => {
const header = document.querySelector('.sticky-header');
const observer = new IntersectionObserver(([e]) => {
e.target.classList.toggle('bottom-shadow', e.intersectionRatio < 1);
}, {
threshold: [1]
});

if (header) {
observer.observe(header);
}
}, []);
  1. 通过一个绝对定位的元素显隐判断是否吸顶,不太通用,但是
<div className='sticky-content-container'>
<div className='sticky-header'></div>
<div id="observer-target"></div>
<div className="scroll-content"></div>
</div>
#observer-target {
position: absolute;
height: 1px;
}
useEffect(() => {
const header = document.querySelector('.sticky-header') as HTMLDivElement;
const observerTarget = document.querySelector('#observer-target');
const observer = new IntersectionObserver(([e]) => {
header.classList.toggle('bottom-shadow', !e.isIntersecting);
}, {
root: document.querySelector('.sticky-content-container'),
rootMargin: '-68px 0px 0px 0px'
});

if (observerTarget) {
observer.observe(observerTarget);
}
}, []);