Vue中实现父section固定时子div滚动的效果问题
实现粘性滚动区块:滚动到section时固定,内部滚动到底后恢复普通状态
需求:
- 当页面滚动到指定
<section>时,该section变为sticky固定状态,其内部的<div>可独立滚动 - 当内部
<div>滚动到底部后,该section恢复为普通区块,随页面向上滚动移出视野
已搭建基础结构,但未实现预期效果,目标效果参考cred.club网站的对应交互。
现有代码
HTML
... 其他前置区块 <section ref="stickyDiv" class="scroller py-sm-128"> <div> <div ref="wrapper" @scroll="scrollerListener" class="wrapper-box"> <div class="d-flex px-sm-128"> <h3 class="section-heading">Access to all the exclusive opportunities</h3> <div class="col-sm-6"> <img class="img-fluid" src="https://res.cloudinary.com/stack-finance/image/upload/v1663728853/app-assets/v2/mask_group_590_e5gbgr.png" > </div> </div> <div class="d-flex px-sm-128"> <h3 class="section-heading">Access to all the exclusive opportunities</h3> <div class="col-sm-6"> <img class="img-fluid" src="https://res.cloudinary.com/stack-finance/image/upload/v1663728853/app-assets/v2/mask_group_590_e5gbgr.png" > </div> </div> <div class="d-flex px-sm-128"> <h3 class="section-heading">Access to all the exclusive opportunities</h3> <div class="col-sm-6"> <img class="img-fluid" src="https://res.cloudinary.com/stack-finance/image/upload/v1663728853/app-assets/v2/mask_group_590_e5gbgr.png" > </div> </div> </div> </div> </section> ... 其他后置区块
Vue 方法
methods: { ... listenBodyScroll(e) { if (this.isMobile) { return; } const stickyDiv = this.$refs.stickyDiv; const wrapper = this.$refs.wrapper; const dim = stickyDiv.getBoundingClientRect(); console.log(dim.y, dim.height, '---scrollTop'); if (dim.y <= 0) { if (Math.ceil(dim.height) <= Math.abs(dim.y)) { stickyDiv.style.position = 'relative'; stickyDiv.style.top = 'auto'; stickyDiv.style.height = 'auto'; wrapper.style.overflowY = 'hidden'; wrapper.scrollTop = wrapper.scrollHeight; return; } wrapper.focus(); stickyDiv.style.position = 'sticky'; stickyDiv.style.top = '100px'; stickyDiv.style.height = '100vh'; wrapper.style.overflowY = 'auto'; } }, scrollerListener({ target: { scrollTop, offsetHeight, scrollHeight, style } }) { if (this.isMobile) { return; } const stickyDiv = this.$refs.stickyDiv; if ((Math.ceil(scrollTop) + offsetHeight) >= scrollHeight) { stickyDiv.style.position = 'relative'; stickyDiv.style.top = 'auto'; stickyDiv.style.height = 'auto'; style.overflowY = 'hidden'; console.log('bottom!'); } } }
Vue 指令 v-scroll
Vue.directive('scroll', { inserted(el, binding) { const f = function(evt) { if (binding.value(evt, el)) { window.removeEventListener('scroll', f); } }; window.addEventListener('scroll', f); } });
解决方案
现有代码的核心问题在于滚动监听的逻辑判断不够准确,以及状态切换时的样式重置不完整。以下是修正后的实现:
1. 调整CSS基础样式
给容器设置基础样式,避免布局跳动和滚动条异常:
.wrapper-box { height: auto; overflow: hidden; scrollbar-width: thin; } .scroller { min-height: 100vh; }
2. 修正Vue滚动监听逻辑
methods: { ... listenBodyScroll(e) { if (this.isMobile) return; const stickyDiv = this.$refs.stickyDiv; const wrapper = this.$refs.wrapper; const rect = stickyDiv.getBoundingClientRect(); const viewportHeight = window.innerHeight; const shouldBeSticky = rect.top <= 100; const hasScrolledPast = rect.bottom <= 0; if (shouldBeSticky && !hasScrolledPast) { stickyDiv.style.position = 'sticky'; stickyDiv.style.top = '100px'; stickyDiv.style.height = `${viewportHeight - 100}px`; wrapper.style.overflowY = 'auto'; wrapper.style.height = '100%'; } else if (hasScrolledPast) { stickyDiv.style.position = 'relative'; stickyDiv.style.top = 'auto'; stickyDiv.style.height = 'auto'; wrapper.style.overflowY = 'hidden'; wrapper.style.height = 'auto'; } else { stickyDiv.style.position = 'relative'; stickyDiv.style.top = 'auto'; stickyDiv.style.height = 'auto'; wrapper.style.overflowY = 'hidden'; wrapper.style.height = 'auto'; } }, scrollerListener({ target }) { if (this.isMobile) return; const stickyDiv = this.$refs.stickyDiv; const { scrollTop, offsetHeight, scrollHeight } = target; if (Math.ceil(scrollTop) + offsetHeight >= scrollHeight) { stickyDiv.style.position = 'relative'; stickyDiv.style.top = 'auto'; stickyDiv.style.height = 'auto'; target.style.overflowY = 'hidden'; target.style.height = 'auto'; window.dispatchEvent(new Event('scroll')); } } }
3. 优化Vue指令
修改为持续监听滚动,并增加销毁时的清理逻辑:
Vue.directive('scroll', { inserted(el, binding) { const handleScroll = (evt) => { binding.value(evt, el); }; window.addEventListener('scroll', handleScroll); el._scrollListener = handleScroll; }, unbind(el) { window.removeEventListener('scroll', el._scrollListener); } });
关键改进点
- 精准计算粘性触发和退出的边界条件,避免状态频繁切换
- 粘性状态下内部容器高度匹配剩余视口,滚动体验更流畅
- 内部滚到底部后手动触发页面滚动,确保section能顺利随页面移出
- 增加监听清理逻辑,避免内存泄漏
内容的提问来源于stack exchange,提问作者Yashwardhan Pauranik
相关产品推荐
相关产品推荐

