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

如何实现粘性Section进入视口时的联动滚动效果?

实现粘性Section内部内容随Body滚动联动的方案

核心思路

  • 用IntersectionObserver判断粘性section是否处于激活状态(即sticky定位生效,section固定在视口内)
  • 监听wheel事件(而非scroll事件),该事件可直接获取滚动增量deltaY,且TypeScript的WheelEvent类型原生包含该属性
  • 当section激活时,阻止body默认滚动,将滚动增量传递给内部.content元素;同时判断.content是否已滚动到边界,若到达边界则恢复body滚动

1. 解决TypeScript类型问题

你遇到的deltaY属性不存在的问题,是因为误将事件类型指定为通用Event。只需在监听wheel事件时,明确事件类型为WheelEvent即可:

document.addEventListener('wheel', (e: WheelEvent) => {
  // 此处可正常访问e.deltaY
});

2. 监听Section的粘性激活状态

通过IntersectionObserver结合getBoundingClientRect精准判断section是否进入sticky生效状态:

const section = document.querySelector('section') as HTMLElement;
const content = section.querySelector('.content') as HTMLElement;
let isStickyActive = false;

const observer = new IntersectionObserver(
  ([entry]) => {
    const rect = section.getBoundingClientRect();
    // 当section固定在top:80px位置且未完全离开视口时,判定为激活状态
    isStickyActive = Math.abs(rect.top - 80) < 1 && entry.isIntersecting;
  },
  {
    root: null,
    threshold: [0, 0.5, 1],
    rootMargin: '-80px 0px' // 匹配sticky的top值,调整交叉判断边界
  }
);

observer.observe(section);

3. 实现滚动联动逻辑

在wheel事件中,根据激活状态控制滚动目标,并处理边界判断:

document.addEventListener('wheel', (e: WheelEvent) => {
  if (!isStickyActive) return;

  // 判断.content是否到达滚动边界
  const isAtTop = content.scrollTop === 0;
  const isAtBottom = content.scrollTop + content.clientHeight >= content.scrollHeight - 1; // 留1px误差避免渲染精度问题

  // 若已到达边界,恢复body原生滚动
  if ((e.deltaY < 0 && isAtTop) || (e.deltaY > 0 && isAtBottom)) {
    return;
  }

  // 阻止body滚动,转而滚动内部.content
  e.preventDefault();
  content.scrollBy({
    top: e.deltaY,
    behavior: 'auto' // 保持原生滚动速度
  });
});

完整代码示例

HTML

<section>
  <div class="content">
    <div class="child">Item 1</div>
    <div class="child">Item 2</div>
    <!-- 省略其余child元素 -->
    <div class="child">Item 21</div>
  </div>
</section>

CSS

section {
  min-height: 682px;
  position: sticky;
  top: 80px;
  border: 1px solid #ccc;
}
.content {
  height: 618px;
  overflow-y: scroll;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.child {
  height: 120px;
  background: #f5f5f5;
  border: 1px solid #eee;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* 测试用:给body添加足够高度 */
body {
  height: 3000px;
  padding-top: 100px;
}

TypeScript

const initStickyScroll = () => {
  const section = document.querySelector('section') as HTMLElement;
  const content = section.querySelector('.content') as HTMLElement;
  if (!section || !content) return;

  let isStickyActive = false;

  const observer = new IntersectionObserver(
    ([entry]) => {
      const rect = section.getBoundingClientRect();
      isStickyActive = Math.abs(rect.top - 80) < 1 && entry.isIntersecting;
    },
    {
      root: null,
      threshold: [0, 0.5, 1],
      rootMargin: '-80px 0px'
    }
  );

  observer.observe(section);

  const handleWheel = (e: WheelEvent) => {
    if (!isStickyActive) return;

    const isAtTop = content.scrollTop === 0;
    const isAtBottom = content.scrollTop + content.clientHeight >= content.scrollHeight - 1;

    if ((e.deltaY < 0 && isAtTop) || (e.deltaY > 0 && isAtBottom)) {
      return;
    }

    e.preventDefault();
    content.scrollBy({
      top: e.deltaY,
      behavior: 'auto'
    });
  };

  document.addEventListener('wheel', handleWheel);

  // 提供清理监听的方法
  return () => {
    observer.disconnect();
    document.removeEventListener('wheel', handleWheel);
  };
};

// 初始化功能
initStickyScroll();

关键细节说明

  • 选用wheel事件:相比scroll事件,它能在滚动触发时直接获取增量,更适合控制滚动目标切换
  • 边界判断留误差:浏览器渲染精度问题可能导致scrollTop + clientHeight与scrollHeight存在微小差值,减1px可避免误判
  • rootMargin配置:匹配sticky的top值,让交叉判断更精准地捕捉section进入粘性状态的时机

内容的提问来源于stack exchange,提问作者Antonio Longo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 04:56:01