You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 10:35:20