Jump to content
Search Community

tl.reverse() has problems when the DOM is loading twice

Asored test
Moderator Tag

Recommended Posts

I refer to the following post: 

In order to keep the forum clear, I created a new thread because the last question of the thread is about a different issue. That way others looking for it can find the solution faster.

 

The issue is the following: In the environment I need to integrate a gsap animation currently is a small bug. The DomContentLoaded eventListener fires twice. I can't change this behavior and I need to play with it.

 

This seems to be a problem for the tl.reverse(). The reverse animation stops until finishing. To make it more clear: I animate a translateX from 180 to 0..and the reverse() stops for example on 15. I think this is because there is triggered a second reverse() until the first is finished. Is there a way to say: "Hey! Please ignore other requests and animate only once"? Something like a force function to definitely finish the reverse animation.

 

Or how could I handle a such scenario?

 

Link to comment
Share on other sites

Hi @Asored,

 

As far as I know the DOMContentLoaded event should fire just once, unless some package or library that you're using in your setup is adding extra resources that are adding an extra script/link tag to the DOM, but it seems very weird to me. I know that iframes can cause the event to trigger more than once.

 

The solution for your specific issue could be to use the isTweening method in order to see if the target is being animated:

if (!gsap.isTweening("#id")) {
  tl.reverse();
}

If the element with that ID is not currently being animated by a GSAP instance, the code inside the if block will be called, otherwise it'll be ignored.

 

Finally if possible, please provide a minimal demo so we can take a better look at this and see what could be the issue.

 

Happy Tweening!!!

Link to comment
Share on other sites

I've never heard of the DOMContentLoaded event firing twice, and I'm confident that has nothing to do with GSAP. 

 

6 hours ago, Asored said:

This seems to be a problem for the tl.reverse(). The reverse animation stops until finishing. To make it more clear: I animate a translateX from 180 to 0..and the reverse() stops for example on 15. I think this is because there is triggered a second reverse() until the first is finished. Is there a way to say: "Hey! Please ignore other requests and animate only once"? Something like a force function to definitely finish the reverse animation.

No, it doesn't matter if you called .reverse() on the animation a bunch of times while it's running - it would just continue going in that direction. It's sorta like saying "go in the backwards direction", and if it's already going in that direction it doesn't really do anything different - it just ensures that the playhead is going in that direction. Here's proof: 

See the Pen WNJXZmW?editors=0010 by GreenSock (@GreenSock) on CodePen

 

That calls reverse() every 500ms on that tween. Notice it makes no difference at all - it just keeps going and is not interrupted.

 

I would not add the .isTweening() condition in there. I'm concerned you've got some other fundamental problem going on in your code, and that condition may just sorta cover up the real problem (I actually doubt it'll have any effect whatsoever). 

 

My suspicion is that you are actually creating multiple conflicting tweens. And the first one has already started moving the element when the second one starts, thus that "starting" value is affected. So it's likely a logic problem in your code. A minimal demo is essential in troubleshooting. 

Link to comment
Share on other sites

35 minutes ago, GreenSock said:

I've never heard of the DOMContentLoaded event firing twice, and I'm confident that has nothing to do with GSAP. 

 

No, it doesn't matter if you called .reverse() on the animation a bunch of times while it's running - it would just continue going in that direction. It's sorta like saying "go in the backwards direction", and if it's already going in that direction it doesn't really do anything different - it just ensures that the playhead is going in that direction. Here's proof: 

 

 

 

That calls reverse() every 500ms on that tween. Notice it makes no difference at all - it just keeps going and is not interrupted.

 

I would not add the .isTweening() condition in there. I'm concerned you've got some other fundamental problem going on in your code, and that condition may just sorta cover up the real problem (I actually doubt it'll have any effect whatsoever). 

 

My suspicion is that you are actually creating multiple conflicting tweens. And the first one has already started moving the element when the second one starts, thus that "starting" value is affected. So it's likely a logic problem in your code. A minimal demo is essential in troubleshooting. 

 

Thanks so much for your words! And also you, Rodrigo! Awesome people here in the forum! :)

 

Regarding the problem I have, I badly cannot provide a codepen demo, because this plays inside a WordPress installation with a Page Builder. The Page Builder itself currently has a bug which loads the DOM content twice. (iframe). In the frontend view, where the DOM content is loading once, the reverse animation works well. But in the backend (builder) view, where the DOM is loaded twice, I get this strange behavior. As workaround I tried to force the javascript function where the animation was created only to load on the first DOM load..and yea, the reverse works then also in the builder view. This shows me: it really has anything to do with the double DOM loading. Very strange.

 

I'll post the code from my javascript function. Maybe this helps. By the way. I see its a good practice to create the timeline outside of the for loop. In this case its not possible because I need to integrate date from the looped items. Or do you have a better idea?

 

Here the code:

 

async function gsapExample() {

  gsap.utils.toArray('.elem-flip-wrapper').forEach(flip => {
      
      let wrapper = flip;
      let box = wrapper.querySelector(".elem-flip-box");
      let front = wrapper.querySelector('.elem-flip-front');
      let back = wrapper.querySelector('.elem-flip-back');

      let animationType = flip.getAttribute('data-type');
      let duration = flip.getAttribute('data-duration');
      let delay = flip.getAttribute('data-delay');
      let trigger = flip.getAttribute('data-trigger');
      let onComplete = flip.getAttribute('data-oncomplete');
      let onStart = flip.getAttribute('data-onstart');
      let onReverseComplete = flip.getAttribute('data-onreversecomplete');

      animationType = animationType ? animationType : 'flip';
      duration = duration ? duration : 1;
      delay = delay ? delay : 0;
      trigger = trigger ? trigger : 'hover';
      onComplete = onComplete ? new Function (onComplete) : '';
      onStart = onStart ? new Function (onStart) : '';
      onReverseComplete = onReverseComplete ? new Function (onReverseComplete) : '';

      const tl = gsap.timeline({ paused: true,
        onStart: function() {
          typeof onStart == 'function' ? onStart() : '';
        },
        onComplete: function() {
            typeof onComplete == 'function' ? onComplete() : '';
        },
        onReverseComplete: function() {
            typeof onReverseComplete == 'function' ? onReverseComplete() : '';
        }
      });

      gsap.set(wrapper, {
          transformStyle: "preserve-3d",
          transformPerspective: 1000
      });
      gsap.set(box, {
          transformStyle: "preserve-3d",
          transformOrigin: "50% 50%",
      });
      gsap.set(back, {
          rotationY: 180, rotationZ: 180
      });

      switch (animationType) {
          case 'flip':
              tl.to(front, { rotationX: 180, duration: duration }, delay);
              tl.to(back, { rotationX: 180, duration: duration }, delay);
              break;
          case 'fade':
              gsap.set(back, { rotationX: 180 });
              tl.to(front, { opacity: 0, duration: duration }, delay);
              tl.to(back, { opacity: 1, duration: duration }, delay);
              break;
          case 'fadeShrink':
              wrapper.style.overflow = 'hidden';
              gsap.set(back, { rotationX: 180 });
              tl.to(front, { scale: .8, opacity: 0, duration: duration/2 }, delay);
              tl.to(back, { opacity: 1, duration: duration }, delay);
              break;
          case 'fadeUp':
              wrapper.style.overflow = 'hidden';
              gsap.set(back, { rotationX: 180, translateY: 500, opacity: 0 });
              tl.to(front, { opacity: 1, rotationX: 180, duration: duration }, delay);
              tl.to(back, { translateY: 0, opacity: 1, duration: duration }, delay);
              break;
          case 'overlay':
              wrapper.style.overflow = 'hidden';
              gsap.set(back, { rotationX: 180, translateY: 500, zIndex: 1 });
              tl.to(back, { translateY: 0, duration: duration }, delay);
              break;
          case 'overlayShrink':
              wrapper.style.overflow = 'hidden';
              gsap.set(back, { rotationX: 180, translateY: 500, zIndex: 1, scale: .9 });
              tl.to(back, { translateY: 0, duration: duration }, delay);
              break;
          default:
              break;
      }

      switch (trigger) {
          case 'hover':
              flip.addEventListener("mouseover", event => {
                  tl.play();
              });
              flip.addEventListener("mouseleave", event => {
                  tl.reverse();
              });
              break;
          case 'click':
              flip.addEventListener("click", event => {
                  if (flip.classList.contains('active')) {
                      tl.reverse();
                      flip.classList.remove('active');
                  } else {
                      flip.classList.add('active');
                      tl.play();
                  }
              });
          default:
              break;
      }
  })
}

 

Link to comment
Share on other sites

Hey there!

 

It's not so much 'best practice'. If you're planning on creating a timeline with multiple animations on it, and you're adding the animations by looping around, the timeline will need to be defined outside the loop - otherwise you'll just be creating multiple timelines every time you loop with one animation on each. But maybe you're aiming for that here? Hard to tell exactly as I don't know what the end aesthetic result is you're going for without seeing a demo.

Quote

By the way. I see its a good practice to create the timeline outside of the for loop

 

FYI - Minimal demos don't have to exactly represent your live example. In fact it's better if they're simplified down as much as possible, just some coloured boxes and minimal styling. See if you can recreate the issue with as few dependancies as possible. If not, incrementally add code bit by bit until it breaks. Usually people solve their own issues during this process! If not, then at least we have a reduced test case which greatly increases your chances of getting a relevant answer.

It's very hard (near impossible) for us to troubleshoot an issue just by looking at some isolated GSAP code.

 

Here's a starter CodePen that loads all the plugins. A minimal demo would be hugely helpful 

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

  • Like 1
Link to comment
Share on other sites

46 minutes ago, Cassie said:

Hey there!

 

It's not so much 'best practice'. If you're planning on creating a timeline with multiple animations on it, and you're adding the animations by looping around, the timeline will need to be defined outside the loop - otherwise you'll just be creating multiple timelines every time you loop with one animation on each. But maybe you're aiming for that here? Hard to tell exactly as I don't know what the end aesthetic result is you're going for without seeing a demo.

 

FYI - Minimal demos don't have to exactly represent your live example. In fact it's better if they're simplified down as much as possible, just some coloured boxes and minimal styling. See if you can recreate the issue with as few dependancies as possible. If not, incrementally add code bit by bit until it breaks. Usually people solve their own issues during this process! If not, then at least we have a reduced test case which greatly increases your chances of getting a relevant answer.

It's very hard (near impossible) for us to troubleshoot an issue just by looking at some isolated GSAP code.

 

Here's a starter CodePen that loads all the plugins. A minimal demo would be hugely helpful 

 

 

Hey Cassie! Thanks very much! You're right. I've created a codepen with my code and simplified it removing the switch case blocks to focus on the relevant parts. In general this is what I try to do. A very simple flip box. As you can see, the code works. Also in my WordPress environment in frontend. But exactly the same code does not work correctly in backend when viewing trough the builder which loads it as iframe. The reverse finishes not on 100%, but pauses on 80-90%. Because of a current bug, the builder loads the DOM twice. I thought that this causes the error, because with a workaround for the builder which loads the DOM once, the animation works again, also in the builder.

 

What do you think?

 

See the Pen bGMYaVw by asored (@asored) on CodePen

Link to comment
Share on other sites

9 hours ago, GreenSock said:

My suspicion is that you are actually creating multiple conflicting tweens. And the first one has already started moving the element when the second one starts, thus that "starting" value is affected. So it's likely a logic problem in your code. A minimal demo is essential in troubleshooting. 

I'm sticking with my theory. 

 

If I were you, I'd put a console.log() right before the tween to see if your setup code is running multiple times and if the rotationX values are what you THINK they are. I bet they're not. 

 

You could solve it by using .fromTo() tweens, but that's sort of masking over the real problem. You definitely don't want to be adding multiple event listeners to the same elements and trying to run multiple duplicate animations simultaneously. By the way, I think you want "mouseenter", not "mouseover" (the latter may fire multiple times when you're over the same element - each descendant). 

Link to comment
Share on other sites

9 hours ago, GreenSock said:

I'm sticking with my theory. 

 

If I were you, I'd put a console.log() right before the tween to see if your setup code is running multiple times and if the rotationX values are what you THINK they are. I bet they're not. 

 

You could solve it by using .fromTo() tweens, but that's sort of masking over the real problem. You definitely don't want to be adding multiple event listeners to the same elements and trying to run multiple duplicate animations simultaneously. By the way, I think you want "mouseenter", not "mouseover" (the latter may fire multiple times when you're over the same element - each descendant). 

 

Hi GreenSock! Thanks for your feedback! Good hint with the `mouseenter`. I've completely overseen this.

 

As you recommended, I've included some console.log() to my code to be sure that the code only runs once. Its important in this case that we have to separate the frontend output from the broken backend output where to code does not work as expected.

 

Console – Frontend Output

Creating timeline
Set timeline settings
Create Timeline animations
Wait for event trigger
Mouseenter event triggered
Mouseleave event triggered
Mouseenter event triggered
Mouseleave event triggered

Here everything seems to be okay, the way I would judge it.

 

Console – Backend (where the DOM is loaded twice)

JQMIGRATE: Migrate is installed, version 3.3.2
JQMIGRATE: Migrate is installed, version 3.3.2
Creating timeline
Set timeline settings
Create Timeline animations
Wait for event trigger
Creating timeline
Set timeline settings
Create Timeline animations
Wait for event trigger
Creating timeline
Set timeline settings
Create Timeline animations
Wait for event trigger
(3) Mouseenter event triggered
(3) Mouseleave event triggered

As you can see, in this case the DOM is rendered not twice, but three times! I hope they will fix the bug soon. But for now, I have to find a workaround for my application. 🤔 By the way: man thanks to you and your great community. This forum is just an enrichment! 👏

 

--EDIT--

I've found a temporary workaround by saving the timeline into a variable and checking if is active or not:

flip.addEventListener("mouseleave", event => {
  if (tl.isActive()) {
    return;
  }
  tl.reverse();
});

May really not be the best solution, but I think in this case I can play with the weird DOM loading.

Link to comment
Share on other sites

React does this kind of multiple calling behaviour and we created gsap.context to deal with it. I'm not really sure exactly what's happening here but it may be worth popping your animations in a context and seeing if that helps? Unless there's actually three DOM elements for each DOM element... Sounds like a mess tbh. Probably best bringing it up with the wordpress folks!

https://greensock.com/docs/v3/GSAP/gsap.context()

 

 

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