Jump to content
Search Community

Recommended Posts

Posted

Hi GSAP team,

I’m running into an issue with ScrollTrigger pinning.

  • I have a section with a .menu__image__wrapper that I want to pin while scrolling.

  • On first page load, the pin doesn’t work (the image disappears).

  • If I switch categories in my app (which destroys and re-inits the triggers), the pin suddenly works correctly.

 

 

I also noticed earlier that I had a y transform (GSAP.set(..., { y: ... })) on the pinned element itself. Removing that transform fixed some glitching, but I still sometimes see the pin fail on first load.

 

Video Of Issue:https://www.loom.com/share/f02251051e0d40a5b0595020b76bd8b7?sid=2a13507c-e9aa-4d87-9644-47d2f085eb01

Here’s a simplified version of my setup:

Here is the animation class code for the menuPIN only:

import GSAP from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
GSAP.registerPlugin(ScrollTrigger);

export default class MenuPin {
  constructor() {
    this.mm = null;               // matchMedia instance
    this.triggers = [];           // ScrollTrigger instances
    this.clickHandlers = [];      // mobile click handlers
    this.currentSection = null;

  }

  setupSection(section) {
    if (!section) return;

    const runSetup = () => {
      // destroy previous triggers/spacers
      this.destroy();

      this.currentSection = section;
      this.mm = GSAP.matchMedia();

      const initDesktop = () => {
        this.setupDesktop(section);
      };

      // Desktop
      this.mm.add("(min-width:1024px)", () => {
        const firstImg = section.querySelector(".menu__image");
        if (firstImg && !firstImg.complete) {
          firstImg.addEventListener("load", initDesktop, { once: true });
          setTimeout(initDesktop, 300);
        } else {
          initDesktop();
        }
        return () => { };
      });

      // Mobile
      this.mm.add("(max-width:1023px)", () => {
        requestAnimationFrame(() => this.setupMobile(section));
        return () => { };
      });
    };

    // Run immediately if DOM is ready, otherwise wait for DOMContentLoaded
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", runSetup);
    } else {
      runSetup();
    }
  }

  setupDesktop(section) {
    const imageWrapper = section.querySelector(".menu__image__wrapper");
    if (!imageWrapper) return;

    const images = Array.from(section.querySelectorAll(".menu__image"));
    const imageHeight = section.querySelector(".menu__image").getBoundingClientRect().height
    const items = Array.from(section.querySelectorAll(".menu__item"));
    const itemHeight = section.querySelector(".menu__item").getBoundingClientRect().height
    if (!items.length) return;

    // spacer at the end
    let spacer = section.querySelector(".menu__spacer");
    if (!spacer) {
      spacer = document.createElement("div");
      spacer.classList.add("menu__spacer");
      section.appendChild(spacer);
    }
    spacer.style.height = `${imageHeight - itemHeight - 41}px`;

    const pinDuration = section.scrollHeight;

    // Pin image wrapper
    const pinTrigger = ScrollTrigger.create({
      trigger: section,
      start: "top top",
      end: () => `+=${pinDuration}`,
      pin: imageWrapper,
      pinSpacing: false,
      markers: false,
    });
    this.triggers.push(pinTrigger);

    // Set first image visible right away
    // GSAP.set(images[0], { autoAlpha: 1 });

    // Then set up triggers
    items.forEach((item, i) => {
      const t = ScrollTrigger.create({
        trigger: item,
        start: `top-=25 top`,
        end: `bottom-=25 top`,
        // onEnter: () => this.showImage(images, i),
        // onEnterBack: () => this.showImage(images, i),
        markers: true,
      });
      this.triggers.push(t);
    });

  }

  setupMobile(section) {
    const images = Array.from(section.querySelectorAll(".menu__image"));
    const items = Array.from(section.querySelectorAll(".menu__item"));

    // remove previous handlers
    items.forEach(item => {
      if (item._menuPinHandler) {
        item.removeEventListener("click", item._menuPinHandler);
        delete item._menuPinHandler;
      }
    });

    // add click handlers
    items.forEach((item, i) => {
      const handler = () => this.showImage(images, i);
      item.addEventListener("click", handler);
      item._menuPinHandler = handler;
      this.clickHandlers.push({ el: item, handler });
    });

    // show first image
    // this.showImage(images, 0);
  }

  showImage(images, index) {
    images.forEach((img, i) => {
      GSAP.set(img, {
        autoAlpha: i === index ? 1 : 0,
        border: i === index ? "3px solid red" : "none"
      });
    });

    GSAP.to(images[index], {
      autoAlpha: 1,
      duration: 0.25,
      ease: "expo.out"
    });
  }

  destroy() {
    // kill triggers
    this.triggers.forEach(t => t && t.kill());
    this.triggers = [];
    // remove click handlers
    this.clickHandlers.forEach(({ el, handler }) => {
      el.removeEventListener("click", handler);
      if (el._menuPinHandler) delete el._menuPinHandler;
    });
    this.clickHandlers = [];

    // remove spacer
    if (this.currentSection) {
      const sp = this.currentSection.querySelector(".menu__spacer");
      if (sp) sp.remove();
    }

    this.currentSection = null;

  }
}
import Page from '@classes/Page';
import MenuPin from '@animations/MenuPin';
import MobileCategorySwipe from '@animations/MobileCategorySwipe';
import GSAP from 'gsap';
import { ScrollTrigger } from "gsap/ScrollTrigger";
GSAP.registerPlugin(ScrollTrigger);
export default class Menu extends Page {
  constructor() {
    super({
      element: '.menu',
      elements: {
        wrapper: '.menu__wrapper',
        section: '.menu__section',
        button: '.menu__category__label',
      },
    });
  }

  create() {
    super.create();

    this.menuPin = new MenuPin();

    const categoryEl = document.querySelector(".menu__category");
    if (categoryEl) {
      new MobileCategorySwipe({ element: categoryEl });
    }

    const firstSection = document.querySelector(".menu__section.--active");
    if (firstSection) {
      this.menuPin.setupSection(firstSection);
    }

    // bind buttons
    this.elements.button.forEach(btn => {
      btn.addEventListener("click", () => {
        const category = btn.dataset.category;
        this.showCategory(category);
      });
    });
  }
  showCategory(category) {
    // Hide all sections first
    this.elements.section.forEach(section => {
      const isActive = section.dataset.category === category;
      if (isActive) {
        section.classList.add('--active');
        console.log(section);
        // Make section visible so ScrollTrigger can measure
        GSAP.set(section, { autoAlpha: 1 });

        // Destroy old triggers before setting up the new one
        if (this.menuPin) this.menuPin.destroy();

        // Setup the pin/animations for the new section

        this.menuPin.setupSection(section);

        // Refresh ScrollTrigger after layout changes
        requestAnimationFrame(() => ScrollTrigger.refresh());
      } else {
        section.classList.remove('--active');
        GSAP.set(section, { autoAlpha: 0 });
      }
    });

    // Update buttons
    this.elements.button.forEach(btn => {
      const isActive = btn.dataset.category === category;
      btn.classList.toggle('--active', isActive);
      GSAP.to(btn, { color: isActive ? "#000" : "#CBC4B1", duration: 0.5, ease: "expo.out" });
    });
  }

}
Posted

Without a minimal demo, it's very difficult to troubleshoot; the issue could be caused by CSS, markup, a third party library, a 3rd party script, etc. Would you please provide a very simple CodePen or Stackblitz that illustrates the issue? 

 

Please don't include your whole project. Just some colored <div> elements and the GSAP code is best. See if you can recreate the issue with as few dependencies as possible. Start minimal and then incrementally add code bit by bit until it breaks. Usually people solve their own issues during this process! If not, at least we have a reduced test case which greatly increases your chances of getting a relevant answer.

 

See the Pen aYYOdN by GreenSock (@GreenSock) on CodePen.

that loads all the plugins. Just click "fork" at the bottom right and make your minimal demo

 

Using a framework/library like React, Vue, Next, etc.? 

CodePen isn't always ideal for these tools, so here are some Stackblitz starter templates that you can fork and import GSAP as shown in the Install Helper in our Learning Center : 

 

Please share the StackBlitz link directly to the file in question (where you've put the GSAP code) so we don't need to hunt through all the files. 

 

Once we see an isolated demo, we'll do our best to jump in and help with your GSAP-specific questions. 

Posted

Thanks,I did upload only the code for that animation I tried doing a codepen its really difficult replicating the issue :(

Posted

Sorry to hear about that, but most likely it means that the problem is not really GSAP related but elsewhere in your setup. A codepen/stackblitz demo allows us not only to play with the code and preview changes and debug easier, but also to isolate the working environment to just HTML/CSS and GSAP. IDK what's the tech stack you're using, if you are using a framework or perhaps a CSS library or perhaps other packages that could be causing this. As already mentioned in this thread there could be a series of alternatives that might be causing this:

 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...