Jump to content
Search Community

Trying to animate dynamically created elements with GSAP

acodeaday test
Moderator Tag

Recommended Posts

I am trying to animate elements that are added to the DOM via javascript with GSAP.

Here is the MRE:

 

So I would like  to change the opacity of the h2s.

 

It is not working because the h2s don't exist when the page first loads, only once the button is clicked.

 

I was hoping that setting it to paused and only having it play on click would fix the problem, but unfortunately not.

 

If I change 

 

timeline
    .to(
        '#recipeContainer h2', {
            opacity: 1
        }
)

 

to 

 

timeline
    .to(
        '#test h2', {
            opacity: 1
        }
)

 

Then it works fine for that element.

 

So it has to be the element being dynamically created but I haven't been able to find a solution.

 

I've been reading the docs and it seems like I might be able to use TimelineMax and onComplete but I can't figure out how to implement it here.

 

Thank you for any help.

See the Pen RwJbrWa by acodeaday (@acodeaday) on CodePen

Link to comment
Share on other sites

Hi @acodeaday welcome to the forum!

 

Have you fixed it already? Because your codepen seems to work, because you're creating the tween when the element is created. 

 

I've added some more properties to your tween and moved the duration from the timeline to the tween and everything seems to work like it should. Personally I've never set the duration of a whole timeline, just individual tweens. It seems like you can set it (see docs), but it probably doesn't get you the result you're after. 

 

See the Pen XWYrKrg?editors=0010 by mvaneijgen (@mvaneijgen) on CodePen

Link to comment
Share on other sites

Sorry, it may not be clear, I edited the codepen and got that working, but have not got the full version on my site working.

 

Initially it was not working as I had timeline.to() outside of the event listener.

 

I was getting the error GSAP target #recipeContainer h2 not found.

 

Once I added it into the event listener it worked.

 

However it doesn't work on my website, it does work on my website if i use a test div with already defined div/h2 but not if i try to use the dynamic elements.

 

then i still face the element not found error.

 

The only thing I can think of is how the getRecipe() function runs.

 

Could it be that it is because the dynamic content that is added on the real site is from an api call that is parsed and the recipe titles are extracted from an array of objects then wrapped in the <h2> tags? So maybe it delays it further which is why it cannot be found? 

 

This is the code for getRecipe

 

function getRecipe(){
let ingredients = document.querySelector('#ingredient').value
// let numberOfMissingIngredients = document.querySelector('#missingIngredients').value
 
//Url of spoonacular api, with authentication key(got by making an account). Type in ingredients to get an Array of Recipe titles
let url =`https://api.spoonacular.com/recipes/complexSearch?&apiKey=2d7e0ded8af74b1897224317ce6c662c&includeIngredients=${ingredients}&addRecipeInformation=true&instructionsRequired=true&number=20&fillIngredients=true&addRecipeNutrition=true`
fetch(url)
.then(res => res.json()) // parse response as JSON
.then(data => {
console.log(data) //get the response(array) of the api
console.log(data.results.length) //title of the first recipe
//Add the the array of recipe objects to the global variable for access later
recipeArray = data.results;
console.log(`recipe array before ${recipeArray}`)
//sort the recipes by least to most missing ingredients
recipeArray = recipeArray.sort((a,b) => a.missedIngredientCount - b.missedIngredientCount)
 
console.log(`recipe array after ${recipeArray}`)
//This is a function that wraps the titles of the returned recipes in <h2> and <a> tags to input into the dom
let recipeTitles = function(){
return recipeArray.map((el, i) => `<h2><a href="#" class="recipeTitles" id="${el.id}">${el.title}</a> -- ${el.missedIngredientCount} ingredient${el.missedIngredientCount > 1 ? 's' : ''} missing</h2>`).join('')
}
 
document.querySelector('#recipeTitleContainer').innerHTML = recipeTitles()
//data.map(el => el === `<p>${el.title}</p>`)
 
 
 
 
})
.catch(err => {
console.log(`error ${err}`)
});
}

 

 

Link to comment
Share on other sites

That function is doing some fetch stuff and is async, so you need to make sure your other code waits for that to be done so that the elements actually exist before you try animating them. 

 

It's pretty tough to troubleshoot without a minimal demo that clearly shows the issue. If you'd like more help, please make sure you provide one of those (like a CodePen or CodeSandbox that clearly illustrates the problem). 

 

Here's a starter CodePen that loads all the plugins. Just click "fork" at the bottom right and make your minimal demo

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

 

If you're using something like React/Next/Nuxt/Gatsby or some other framework, you may find CodeSandbox easier to use. 

 

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

image.png

Link to comment
Share on other sites

13 hours ago, GSAP Helper said:

That function is doing some fetch stuff and is async, so you need to make sure your other code waits for that to be done so that the elements actually exist before you try animating them. 

 

This seems to work. I would build something more robust with async and await to be sure that the code only runs when the items are drawn. And again I would not set your duration in the timeline, but set it per tween.  I can't test it any more, because your API has reached its 150 daily limit. 

 

See the Pen ExRYRRZ?editors=0011 by mvaneijgen (@mvaneijgen) on CodePen

 

Edit: I've added some code to check if the API fails to add a fake h2 so that the tween always works. I would not use this code, but the logic is clear, only create your tween when you know all the items are created. 

  • Like 1
Link to comment
Share on other sites

Sorry I am very new to greensocks, and relatively new to coding in general.

 

I don't know async and await is it a greensocks thing or a javascript thing? I will have to try to look up how to implement it. 

 

Would it be something that is applied to the getRecipe() function or the tween?

 

When you say the duration shouldn't be applied to the timeline, should it be like this instead?

timeline
.to(
'#recipeTitleContainer h2', {
duration: 3,
opacity: 1
});

 

And I have updated the API key, there should be 150 more searches now.

Link to comment
Share on other sites

async and await are javascript functions, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function these are ways to wait for content and do something when something else has happend. 

 

Your issue is that you don't know when the data has come back so you need to await that and run your logic only when that happens. 

 

Quote

When you say the duration shouldn't be applied to the timeline, should it be like this instead?

Yes, like I've updated in my example.

 

Quote

And I have updated the API key, there should be 150 more searches now.

For me it still isn't working, but that doesn't really matter the concept will be still the same. 

  • Like 2
Link to comment
Share on other sites

I have been trying to implement async await but am yet to have any luck. here is the code I have at the moment:

 

See the Pen eYKOKJO by acodeaday (@acodeaday) on CodePen

 

I can see from the console log that getRecipe is running, but timeline.to is not because it can't find the target.

 

I thought maybe it's because I don't use a promise, but from the mozilla link you sent me it says

If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise."

 

I tried to implement a promise into the get recipe function, but couldn't get it to work.

 

Then i thought maybe it's because getRecipe doesn't return anything, but I wasn't sure what to return.

 

If you could help me understand this a bit better I would really appreciate it. 

 

Sorry if this is the wrong place to ask about this.

Link to comment
Share on other sites

Hey, sorry no I don't have time to explain async await. It was a suggestion I did because it could be a robust way to handle these kinds of logic puzzles, but I'm no hero in them my self, but there are loads of tutorials out there that dive really deep in to them. On this forum we focus on  GSAP related questions and can't dive in to general Javascript logic.

 

I would just stick to what works right now, there is always time to improve your code later. So I would just handle the animation as soon as you know the elements are created.

 

See the Pen rNKNpBd?editors=0110 by mvaneijgen (@mvaneijgen) on CodePen

Link to comment
Share on other sites

Mitchel intentionally omitted the stagger above, btw..

This is the code:


 

document.querySelector("#btn").addEventListener("click", () => {
  getRecipe();
  console.log("first done");
});

function getRecipe() {
  let url = `https://api.spoonacular.com/recipes/complexSearch?&apiKey=596b9db3179446a09d18a9eb32c1f801&includeIngredients=ham,egg&addRecipeInformation=true&instructionsRequired=true&number=20&fillIngredients=true&addRecipeNutrition=true`;
  fetch(url)
    .then((res) => res.json()) // parse response as JSON
    .then((data) => {
      recipeArray = data.results;

      //This is a function that wraps the titles of the returned recipes in <h2> and <a> tags to input into the dom
      let recipeTitles = function () {
        return recipeArray
          .map(
            (el, i) =>
              `<h2><a href="#" class="recipeTitles" id="${el.id}">${el.title}</a></h2>`
          )
          .join("");
      };

      document.querySelector("#recipeContainer").innerHTML = recipeTitles();

      gsap.from("#recipeContainer h2", {
        opacity: 0,
        duration: 3,
        stagger:.3
      });
    })
    .catch((err) => {
      console.log(`error ${err}`);
    });
}


:) Credits to him, btw

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