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

如何使用GSAP ScrollTrigger优化堆叠面板的平滑固定启动、滚动过渡及粘性头部表现

如何使用GSAP ScrollTrigger优化堆叠面板的平滑固定启动、滚动过渡及粘性头部表现

Hey 👋,我刚好做过类似的滚动堆叠面板需求,结合你给出的代码,咱们来一步步优化平滑固定启动、滚动过渡的流畅度,还有粘性头部的配合逻辑,让整个交互更丝滑:

一、优化固定启动的平滑度

你已经用到了anticipatePin: 1,这是个好起点,但还可以从这几个方向调整:

  • 微调触发位置,让固定时机更自然
    原代码的start: "center-=30% 50%"可以根据实际视觉需求调整,比如改成start: "top+=100px 50%",让面板刚进入视口上方一点就开始准备固定,避免突兀的固定动作。

  • 给固定动作添加缓动过渡
    不要直接依赖ScrollTrigger默认的pin行为,而是配合GSAP的tween来控制面板进入时的透明度和位置过渡,替换原来生硬的gsap.set为带缓动的动画。

  • 优化scrub值,平衡平滑度和响应性
    原代码的scrub: 2偏慢,改成scrub: 0.6会让滚动和动画的响应更同步,同时保持平滑感(可以根据需求在0.3-1之间调整)。

二、优化面板滚动过渡的流畅性

堆叠面板的显隐和层级管理很关键,这里做几个优化:

  • 给面板显隐添加淡入淡出动画
    把原来直接切换透明度的gsap.set改成带缓动的淡入淡出,避免生硬的视觉跳变:

    gsap.to(panel, { autoAlpha: 1, duration: 0.5, ease: "power2.out" });
    
  • 动态管理z-index,确保堆叠层级正确
    取消注释你原来的panel.style.zIndex = ${100 - index};,或者在ScrollTrigger的回调里根据激活状态调整层级,让当前固定的面板始终在最上层,避免被其他面板遮挡。

三、优化粘性头部的联动逻辑

粘性头部和ScrollTrigger的配合要避免setTimeout的临时 hack,改用ScrollTrigger的原生回调来精准控制:

  • 移除setTimeout,用原生refresh回调
    原代码里的延迟刷新可以替换成在ScrollTrigger创建完成后直接调用ScrollTrigger.refresh(),如果有异步加载的内容,可以在内容加载完成后再调用,确保布局稳定后计算滚动位置。

  • 精准控制粘性头部的显示/隐藏
    在最后一个面板的回调里,用GSAP的tween控制头部的位移或透明度,而不是直接切换class,让过渡更平滑:

    gsap.to(stickyHeader, { y: -headerHeight, duration: 0.4, ease: "power2.in" });
    

四、完整优化后的代码

结合上面的所有优化,修改后的完整代码如下:

ngAfterViewInit(): void {
  function updateActiveButton(activeIndex: number) {
    buttons.forEach((btn) => {
      const index = Number(btn.getAttribute('data-index'));
      btn.classList.toggle('active', index === activeIndex);
    });
  }

  gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);
  const panels = gsap.utils.toArray<HTMLElement>(".panel");
  const buttons = document.querySelectorAll('.button-group button'); // menu buttons
  const header = document.querySelector('.products-header-panel') as HTMLElement;
  const stickyHeader = document.querySelector(".products-header-panel");
  const headerHeight = header?.offsetHeight || 0;

  panels.forEach((panel, index) => {
    const isLast = index === panels.length - 1;
    // 初始化层级,确保面板堆叠顺序正确
    panel.style.zIndex = `${90 - index}`;

    ScrollTrigger.create({
      id: `panel-${index}`, // unique ID for each panel
      trigger: panel,
      // 调整触发位置,让固定动作更提前启动
      start: "top+=100px 50%",
      end: isLast ? "bottom bottom" : `+=${panel.offsetHeight}`,
      // 优化scrub值,平衡平滑度和滚动响应速度
      scrub: 0.6,
      pin: panel,
      pinSpacing: false,
      // markers: true, // 调试时可以打开查看触发范围
      // 增强固定前的预判动画,让启动更丝滑
      anticipatePin: 1.2,
      onEnter: () => {
        // 淡入当前面板并置顶层级
        gsap.to(panel, { autoAlpha: 1, duration: 0.5, ease: "power2.out" });
        panel.style.zIndex = "100";
        updateActiveButton(index);
      },
      onLeave: () => {
        // 淡出面版并调整层级
        gsap.to(panel, { autoAlpha: 0, duration: 0.3, ease: "power2.in" });
        panel.style.zIndex = `${90 - index}`;
        
        if (isLast && stickyHeader) {
          // 平滑隐藏粘性头部
          gsap.to(stickyHeader, { y: -headerHeight, duration: 0.4, ease: "power2.in" });
          // 最后一个面板离开时保持可见
          gsap.set(panel, { autoAlpha: 1 });
        }
      },
      onEnterBack: () => {
        // 淡入当前面板并置顶层级
        gsap.to(panel, { autoAlpha: 1, duration: 0.5, ease: "power2.out" });
        panel.style.zIndex = "100";
        updateActiveButton(index);
        
        if (isLast && stickyHeader) {
          // 平滑显示粘性头部
          gsap.to(stickyHeader, { y: 0, duration: 0.4, ease: "power2.out" });
        }
      },
    });
  });

  // Scroll to panel on button click
  buttons.forEach((btn, i) => {
    btn.addEventListener("click", () => {
      const targetPanel = panels[i];
      if (!targetPanel) return;

      // Find trigger by ID
      const trigger = ScrollTrigger.getById(`panel-${i}`);
      if (!trigger) return;

      // 滚动到面板起始位置,适配头部高度避免遮挡
      const scrollToY = trigger.start - headerHeight;
      gsap.to(window, { 
        duration: 0.6, 
        scrollTo: { y: scrollToY }, 
        ease: "power2.out" 
      });
    });
  });

  // 确保布局稳定后刷新ScrollTrigger
  ScrollTrigger.refresh();
}

额外小技巧

  • 如果你想让固定时面板有轻微的视觉层次感,可以在onEnter里添加scale: 1.02的动画,然后在onLeave里恢复scale: 1。
  • 打开markers: true可以直观看到ScrollTrigger的触发范围,方便微调start和end的位置。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 03:10:48