实现Div滚动吸顶并保持原有宽度,解决滚动溢出、闪烁回弹问题
问题核心原因
- 元素切换为fixed定位时会脱离正常文档流,原位置没有元素占位,导致下方内容突然上跳,也就是你遇到的回弹复位问题
- fixed定位的元素
width:100%是相对于浏览器视口计算,不是相对于原来的父级滚动容器,所以会出现尺寸异常、溢出页面的问题 - 给三个元素分别绑定了scroll事件,触发频率过高,加上反复读写DOM样式会触发浏览器重排重绘,导致滚动闪烁
- CSS里重复写position属性(先写absolute又写fixed)属于无效代码,table-row元素设置fixed定位会破坏原生表格布局流,列宽无法和下方表格对齐
修复方案
第一步:优化HTML结构,添加占位元素
每个要吸顶的元素外面套一层占位容器,用来在元素fixed的时候占据原位置,避免高度塌陷:
<!-- 原来的fixedElement外层加占位容器 --> <div class="sticky-placeholder"> <div class="fixedElement justify-between items-center mb-1"> <!-- 原有内容不变 --> </div> </div> <!-- fixedElement1和fixedElement2也做同样处理 --> <div class="sticky-placeholder-table"> <div class="fixedElement1 table-row divide-x divide-gray-200"> <!-- 原有内容不变 --> </div> </div> <div class="sticky-placeholder-filter"> <div class="fixedElement2 table-row divide-x divide-blue-200 bg-blue-100"> <!-- 原有内容不变 --> </div> </div>
第二步:修正CSS
删掉重复的position定义,固定定位时主动继承父容器宽度,表格行吸顶时调整布局避免错乱:
/* 通用占位容器样式,提前占住原高度 */ .sticky-placeholder, .sticky-placeholder-table, .sticky-placeholder-filter { width: 100%; } .fixedElement { display:flex; align-items:center; margin-bottom: .25rem!important; justify-content: space-between; z-index:100; background: #fff; /* 固定时宽度继承父容器,不相对视口 */ width: inherit; } .fixedElement.fixed-active { position: fixed; top: 5px; } .fixedElement1 { display: table-row; box-sizing: border-box; z-index:100; background: #fff; width: inherit; } /* 表格行吸顶时转成flex布局,和下方表格列宽强制对齐 */ .fixedElement1.fixed-active { position: fixed; top: 45px; display: flex; width: inherit; } .fixedElement1.fixed-active .table-cell { flex-shrink: 0; } .fixedElement2 { display: table-row; box-sizing: border-box; z-index:100; background: #fff; width: inherit; } .fixedElement2.fixed-active { position: fixed; top: 100px; display: flex; width: inherit; } .fixedElement2.fixed-active .table-cell { flex-shrink: 0; }
第三步:优化JS逻辑,合并scroll事件加防抖
合并三个scroll监听,主动获取占位容器的位置和宽高赋值给吸顶元素,加防抖减少触发频率避免闪烁:
// 防抖函数,减少滚动事件触发频率 function debounce(func, wait) { let timeout; return function() { const context = this, args = arguments; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); } } $(window).scroll(debounce(function(e) { const scrollTop = $(this).scrollTop(); // 处理第一个吸顶元素 const $placeholder1 = $('.sticky-placeholder'); const $el1 = $('.fixedElement'); // 占位容器高度和宽度同步给元素 $placeholder1.css('height', $el1.outerHeight()); $placeholder1.css('width', $el1.parent().width()); scrollTop > 200 ? $el1.addClass('fixed-active') : $el1.removeClass('fixed-active'); // 处理第二个吸顶元素 const $placeholder2 = $('.sticky-placeholder-table'); const $el2 = $('.fixedElement1'); $placeholder2.css('height', $el2.outerHeight()); $placeholder2.css('width', $el2.parent().width()); if (scrollTop > 240) { $el2.addClass('fixed-active'); // 主动同步列宽和下方表格一致 $el2.find('.table-cell').each(function(index) { const originWidth = $('.table .table-row').not('.fixedElement1, .fixedElement2').eq(0).find('.table-cell').eq(index).outerWidth(); $(this).css('width', originWidth); }) } else { $el2.removeClass('fixed-active').find('.table-cell').css('width', ''); } // 处理第三个吸顶元素 const $placeholder3 = $('.sticky-placeholder-filter'); const $el3 = $('.fixedElement2'); $placeholder3.css('height', $el3.outerHeight()); $placeholder3.css('width', $el3.parent().width()); if (scrollTop > 300) { $el3.addClass('fixed-active'); // 主动同步列宽和下方表格一致 $el3.find('.table-cell').each(function(index) { const originWidth = $('.table .table-row').not('.fixedElement1, .fixedElement2').eq(0).find('.table-cell').eq(index).outerWidth(); $(this).css('width', originWidth); }) } else { $el3.removeClass('fixed-active').find('.table-cell').css('width', ''); } }, 10)); // 10ms防抖,兼顾流畅度和性能
可选更优方案(无需JS)
如果不需要兼容IE11及更早版本浏览器,可以直接用CSS原生的position: sticky实现吸顶,性能更好完全不会闪烁,直接替换原来的fixed相关CSS即可,不需要写任何JS逻辑:
.fixedElement { position: sticky; top: 5px; z-index: 100; background: #fff; } .fixedElement1 { position: sticky; top: 45px; z-index: 99; background: #fff; } .fixedElement2 { position: sticky; top: 100px; z-index: 98; background: #fff; }
内容的提问来源于stack exchange,提问作者Alexandra Claudia
相关产品推荐
相关产品推荐

