Flutter中如何让堆叠粘性头部被最新头部替换?
解决日历粘性头部堆叠问题
问题分析
默认的position: sticky会让每个日期头部滚动到顶部时固定,但多个粘性元素会依次堆叠在顶部,无法自动替换。要实现"最新头部替换顶部旧头部"的效果,需要通过监听元素交叉状态来控制头部的显示逻辑。
解决方案:使用Intersection Observer API
通过一个单独的占位容器作为固定粘性头部,配合Intersection Observer监听每个日期头部的滚动位置,实时更新占位头部的内容,避免堆叠。
修改后的完整代码
HTML
<div class="calendar-container"> <div class="day-section"> <div class="day-header" data-date="2024-05-20">2024年5月20日</div> <div class="event">团队周会</div> <div class="event">项目进度评审</div> </div> <div class="day-section"> <div class="day-header" data-date="2024-05-21">2024年5月21日</div> <div class="event">客户需求沟通</div> <div class="event">代码提交</div> <div class="event">版本发布准备</div> </div> <div class="day-section"> <div class="day-header" data-date="2024-05-22">2024年5月22日</div> <div class="event">测试用例评审</div> <div class="event">线上问题排查</div> </div> </div> <div class="sticky-header-placeholder"></div>
CSS
.calendar-container { max-height: 400px; overflow-y: auto; position: relative; width: 300px; margin: 20px; } .day-header { background-color: #f8f9fa; padding: 10px 15px; border-bottom: 1px solid #dee2e6; font-weight: 600; } .sticky-header-placeholder { position: sticky; top: 0; background-color: #f8f9fa; padding: 10px 15px; border-bottom: 1px solid #dee2e6; font-weight: 600; display: none; z-index: 10; width: 300px; margin-left: 20px; } .day-section { margin-bottom: 15px; } .event { padding: 8px 15px; border: 1px solid #e9ecef; border-radius: 4px; margin: 5px 0; }
JavaScript
const stickyPlaceholder = document.querySelector('.sticky-header-placeholder'); const dayHeaders = document.querySelectorAll('.day-header'); const calendarContainer = document.querySelector('.calendar-container'); // 初始化交叉观察器 const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { const header = entry.target; const rect = entry.boundingClientRect; const containerRect = calendarContainer.getBoundingClientRect(); // 当前头部进入视口顶部区域时,更新占位头部内容 if (entry.isIntersecting && rect.top <= containerRect.top) { stickyPlaceholder.textContent = header.textContent; stickyPlaceholder.style.display = 'block'; } // 当前头部完全离开视口顶部时,切换为下一个头部内容 if (!entry.isIntersecting && rect.bottom <= containerRect.top) { const nextSection = header.parentElement.nextElementSibling; if (nextSection) { const nextHeader = nextSection.querySelector('.day-header'); if (nextHeader) { stickyPlaceholder.textContent = nextHeader.textContent; } } } // 最后一个头部完全离开视口底部时,隐藏占位头部 if (!entry.isIntersecting && rect.bottom <= 0 && header === dayHeaders[dayHeaders.length - 1]) { stickyPlaceholder.style.display = 'none'; } }); }, { root: calendarContainer, threshold: 0.01, rootMargin: '-1px 0px 0px 0px' // 微调触发时机,避免切换闪烁 }); // 监听所有日期头部 dayHeaders.forEach(header => observer.observe(header));
关键逻辑说明
- 替换单个粘性容器:移除每个日期头部的
position: sticky,改用单独的sticky-header-placeholder作为唯一的粘性头部,从根源避免堆叠。 - 交叉状态监听:通过Intersection Observer实时检测每个日期头部与容器的交叉位置,动态更新占位头部的显示内容。
- 边界处理:针对头部进入顶部、离开顶部、最后一个头部离开底部三种场景做了逻辑处理,确保头部切换流畅且符合预期。
内容的提问来源于stack exchange,提问作者rusty
相关产品推荐
相关产品推荐

