Jump to content
Search Community

Timeline behavior with dynamic DOM nodes. How to refresh the targets?

Asored test
Moderator Tag

Recommended Posts

Hey everyone!

 

First of all, and sorry for the Offtopic: this new gsap website is just amazing. Great work!

 

Now about the problem I'm currently fighting with. I've created a minimal demo to understandable reproduce the issue.

 

Demo description in short: When new items with the same class are coming to the DOM, only the old ones are animated.

 

Detailled version:

 

When a timeline is created, targets are saved, which are DOM nodes in the end. If I've researched this correctly, the function uses `querySelectorAll()`. That's good so far, but I'm currently encountering a problem.

 

I have a context where data is dynamically loaded on a page (a facet filter system, where different data are pulled from the server but always have the same class). I want to animate the reloading of these contents by restarting the GSAP timeline with .restart() after the server request. Unfortunately, the timeline does not recognize the new elements, which is understandable up to this point, as it only saved those that were in the DOM at the time the timeline was created.

 

My question: is it somehow possible to update the timeline instance so that it also includes the new elements present in the DOM?

 

Of course, the sensible approach here would be to simply create a new timeline. However, in my case, this is not possible because it's a UI platform where users create the timeline that they can restart in different situations. So, it's a prerequisite that the same timeline is used. Is this somehow possible?

 

I would be very grateful for any help!
 

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

Link to comment
Share on other sites

1 hour ago, Asored said:

First of all, and sorry for the Offtopic: this new gsap website is just amazing. Great work!

Thanks for the kind words! 💚

 

That sounds about right, GSAP is doing exactly what is supposed to do. Some background, for optimizations reasons GSAP stores the elements and styles at render time, so when you do this:

tl.from(".selector", {
  opacity: 0,
  y: 100,
  duration: 0.5
});

The GSAP instance will store only the elements present in the DOM at run time and their styles, so when new ones are added, those are not considered because all you're doing is restarting the Timeline that only has those original elements to put it in some way.

 

If you want to animate the elements that are added and, since you're not using staggers, you can either clear the timeline and add a new instance to it after the new elements are added to the DOM:

const loadMore = () => {
  addItems();
  tl.clear()
    .from(".selector", {
      opacity: 0,
      y: 100,
      duration: 0.5
    })
    .restart();
}

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

 

Another option that doesn't involve clearing the timeline and running the selector again, is to target the specific new element being added and add a new instance to the timeline at zero seconds using the position parameter:

const loadMore = () => {
  addItems();
  tl.restart();
}

const addItems = () => {
  let newDiv = document.createElement("div");
  newDiv.className = "selector";
  
  let newH1 = document.createElement("h1");
  count = count + 1;
  newH1.textContent = count;
  newDiv.appendChild(newH1);  
  wrapper.appendChild(newDiv);
  
  tl.from(newDiv, {    
    opacity: 0,
    y: 100,
  }, 0);
}

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

Honestly I prefer the second choice, because you don't have to run all the logic and processing for every element,  just the new element. Is worth mentioning that this option works, only if you have that much control over how the elements are being added. If you don't have that much granular control over your app and how the DOM is updated, the first alternative would be the way to go.

 

If you don't have that much control you can get more specific regarding the selector and use the parent element instead of the entire document, like this:

const tl = gsap.timeline();
let count = gsap.utils.toArray(".selector").length;
const wrapper = document.querySelector(".wrapper");
tl.from(".selector", {
  opacity: 0,
  y: 100,
  duration: 0.5
});

const loadMore = () => {
  addItems();
  tl.restart();
}

const addItems = () => {
  let count = document.querySelectorAll('.selector').length;

  let newDiv = document.createElement("div");
  count = count + 1;
  newDiv.className = "selector selector-" + count;
  
  let newH1 = document.createElement("h1");
  newH1.textContent = count;
  newDiv.appendChild(newH1);  
  wrapper.appendChild(newDiv);
  
  tl.from(wrapper.querySelector(".selector-" + count), {    
    opacity: 0,
    y: 100,
  }, 0);
}

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

As you can see there are a few options and if you get creative enough you'll find other ways.

 

Hopefully this helps.

Happy Tweening!

  • Like 2
Link to comment
Share on other sites

Many, many thanks @Rodrigo!

 

Your post helped me to find a sensible and creative solution! 👍

 

Because all the users' tweens are stored in the database, I was able to recreate the timelines simply by grabbing the animation data via tween.vars and other objects. So each new DOM element will create and run a new timeline on loaded.

 

Everything works as expected. Very cool. 🤓

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