Jump to content
Search Community

Angular routing - scrolltrigger not creating when getting back to page component

hello-there test
Moderator Tag

Recommended Posts

I have simple animations on a homepage component that works amazingly when I first visit the page or if I just refresh with F5:
 

  @ViewChild('section1', {static: true}) section1: ElementRef<HTMLDivElement>;
  @ViewChild('section2', {static: true}) section2: ElementRef<HTMLDivElement>;  

  ngOnInit(): void {
    this.initScrollAnimation();
  }

  ngOnDestroy() {
    ScrollTrigger.refresh();
  }

  initScrollAnimation(): void {
    gsap.utils.toArray<HTMLElement>(".defil").forEach(section => {
          ScrollTrigger.create({
            trigger: section,
            start: 'top top',
            pin: true,
            pinSpacing: false
          });
    });

    gsap.to(this.section1.nativeElement, {
      scrollTrigger: {
        trigger: this.section2.nativeElement
      },
      duration: 1,
      y: 150,
      scale: 0.9
    })
  }

But when I visit another page through angular routing and get back to this one I can't make this 2 scrolltrigger to work. I am new to the scrolltrigger and to gsap and there may be something I'm not understanding and not using well according to angular lifecycle.

If you could help me on this one, I am stuck. Tried to find similar issues but it didn't seem to be the same problem nor work for me.

I also have another minor issue that is not as important to me but when the gsap.to animation triggered if I get back to the top of the page and get back down it doesn't retrigger a second time.

Link to comment
Share on other sites

Codepen is not really ideal to build multiple routes application, which is the step where it seems to break at my point. I tried replicating the problem on a codesandbox but the browser from it didn't seem to render the animations at all, I'll see if I can make a test somewhere else and post it here.

Link to comment
Share on other sites

  • 3 months later...

Just stumbled on this from a long, long search.

 

This is apparently a widespread problem with questions being asked from Stackoverflow, github, to this very forum.. It's one of the toughest debugs I've ever tackled for something that should be kind of simple right? To kill scrolltrigger, to then reinitialize it. Even the GSAP devs think the solution is as simple as the below line inserted some say before executing the route change, some say before reinitializing, etc etc.

ScrollTrigger.getAll().forEach(t => t.kill())

 

The only workaround all would agree is unacceptable, you can find it here, it's to replace the [routerlink] or whatever you are using to route, into an href, which breaks the purposes of an SPA.

 

What's going on with route changes? I've seen this same problem with Next.js users, Vue users, React users, and Angular users. The worst part is that it's a very fickle problem, it's really specific and nothing seems to change anything once scrolltrigger wraps its arms around your code. I just can't find what the problem is. I'm hitting it from every side... I'll keep trying to debug this, but it's very strange!

 

Edit 1: 

I suspect it's related to GSAP's event listeners; by a process of elimination, going from one line of code to the next where I initialize GSAP, the one line that reacts at all to route changes is 

 

ScrollTrigger.addEventListener("refresh", () => sample_function());

Furthermore, when you check here, an engineer at a Belgian company seemed to have discovered a similar issue by accident where the event listeners are not cleaning themselves up when the instances is killed..


I tried to reproduce this: I killed all my scrolltrigger instances via

 

ScrollTrigger.getAll().forEach(t => t.kill())

then I resize my browser, and lo and behold, the listener just picked up a refresh event from the 'dead' instance.

 

While researching I found this nearly identical problem with difficulty to kill the instance

 

Final edit: 

Found a semi-clean working example that worked for me when I copy the exact code they have going on, it'll do the job for now but she's messy. I hope this post can serve as light to anyone lost with this problem, it's definitely not easy

 

To devs that might find this, the scrolltrigger event listener is still listening, you can see for yourself in a console.log with a resize event listener from the other route, it's just a tentative workaround

Link to comment
Share on other sites

I think there might be a misunderstanding at play here...

 

When you add a listener like this: 

ScrollTrigger.addEventListener("refresh", someFunc);

That is not adding it to a particular ScrollTrigger instance - that is a static method that is completely unaffected by this line: 

// kills every individual instance
ScrollTrigger.getAll().forEach(t => t.kill());

So someFunc() would still get called on refresh in this case. That's the proper behavior. You should remove your listeners if you don't want them to get called anymore, like: 

ScrollTrigger.removeEventListener("refresh", someFunc);

 

But reading through your post, I think it may be wise to add a ScrollTrigger.killAll() method that does two things: 

  • Calls kill() on every ScrollTrigger instance (same as ScrollTrigger.getAll().forEach(t => t.kill()))
  • Removes ALL event listeners.

What do you think? 

 

Either way, though, I'm very curious to see a minimal demo that shows things NOT working when you kill all the instances AND you remove your event listeners properly. I suspect that maybe you just didn't realize you needed to remove your static event handlers(?)

  • Like 3
Link to comment
Share on other sites

  • 2 weeks later...

you need to wrap your function in a setTimeout func.

"Invoking setTimeout with a callback, and zero as the second argument will schedule the callback to be run asynchronously, after the shortest possible delay - which will be around 10ms and the JavaScript thread of execution is not busy."

import { Component, OnInit } from '@angular/core';
import {gsap } from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';
gsap.registerPlugin( ScrollTrigger );
@Component({
  selector: 'app-our-community',
  templateUrl: './our-community.component.html',
  styleUrls: ['./our-community.component.scss']
})
export class OurCommunityComponent implements OnInit {
 
  communtyAnimation: any
 
  constructor() { }
 
  ngOnInit(): void {
    setTimeout(() => {
      this.communtyAnimation = this.createAnime()
    }, 0);
  }
 
  createAnime() {
    gsap.to(".community-title", {
      scrollTrigger: {
        trigger: ".community-style",
        start: "top center",
        end: "bottom center",
        toggleActions: "restart reverse restart reverse",
      },
      y: 0,
      ease: "power1.out",
      delay: 0.3,
    });
    gsap.to(".community-img" ,{
      scrollTrigger: {
        trigger: ".community-style",
        start: "top center",
        end: "bottom center",
        toggleActions: "restart reverse restart reverse",
      },
      rotate: 0,
      ease: "power1.out",
      delay: 0.3,
    })
  }

}
  • Thanks 2
Link to comment
Share on other sites

Hey @GreenSock, just saw your reply! I actually ended up dropping Locomotive Scroll for this current project I'm working on, I found that GSAP and its plugins were better suited for the specific project's use case, though Loco has great smoothing. GSAP's performance was better as well, since the smoothing wasn't as buttery, although I ended up finding GSAP's smoothing plugins, they may work just as good and are probably faster.

 

To those that may end up working on a project that can't switch from Locomotive Scroll, I dug up my solution from github.

I should have made a StackBlitz at the time to make things more obvious, but either way I was able to resolve the problem with the following unloading method: 

unload() {
    console.log("Kill", this.horizontal_scroll); // check locomotive scroll instance variable binding
    this.horizontal_scroll.destroy(); // destroy instance of locomotive scroll
    ScrollTrigger.removeEventListener("refresh", this.horizontal_scroll.update()); // remove listener, note this triggers a console.log error, but it works
    ScrollTrigger.scrollerProxy(null); // <- this will get rid of the current scrollerProxy
    ScrollTrigger.clearScrollMemory(); 
    this.horizontal_scroll = null; // destroys locomotive scroll instance variable binding
    ScrollTrigger.getAll().forEach(t => t.kill());
  }

There's many ways to use it, but I used it in the same component as I declared the locomotive scroll instance. You call it right before changing routes/destroying the component that holds your locomotive scroll instance.

  • Thanks 1
Link to comment
Share on other sites

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...