Jump to content
Search Community

Nested Accordion

pietM test
Moderator Tag

Recommended Posts

Hi everyone, 


I have a simple nested accordion almost behaving as planned. My question is how to achieve the "slide up" animation. Because the class is set to "display:none;" I understand that I won't see the animation as written, but is there an alternative way to handle the class with GSAP to achieve the slide up animation? 

 

Any advice is greatly appreciated. 

Thanks for all! 

 

 

 

See the Pen eYWRjXx by evryali (@evryali) on CodePen

Link to comment
Share on other sites

Hi @pietM

I would probably create a timeline animation which I could play() on opening and reverse() on closing, then use an onReverseComplete callback to remove the class.

Lots of different ways to approach this but central to the issue would be not to make the elements display none until you've completed an exit animation.

Does that help?

Link to comment
Share on other sites

3 hours ago, ZachSaucier said:

This came up on StackOverflow. Here's another version of Blake's pen which might be helpful for future readers:

 

Welp. I thought I gave pretty good instructions in the original thread. A little old, but still relevant.

How to toggle tweens in a DRY fashion - GSAP - GreenSock

 

Guess it's time for @Carl make some videos on it.

 

  • Like 2
Link to comment
Share on other sites

18 hours ago, OSUblake said:

Guess it's time for @Carl make some videos on it.

 

😁  I actually favorited this thread when I saw the accordion demo for "future lesson inspiration"

Truth be told, on initial study the code confused me (again).

 

animations.forEach(animate => animate(selected))

 

using map to create an array of functions and then looping through those functions passing in targets... for some reason my brain just doesn't work that way. I had to re-read your "DRY" post carefully and slowly for it to actually click. 

 

I would be more naturally inclined to build something like this closer to what @ZachSaucier provided.

 

The fact that I find the Blake approach so foreign probably means more folks would find it interesting and could benefit from it. 👍

Link to comment
Share on other sites

1 hour ago, Carl said:

The fact that I find the Blake approach so foreign probably means more folks would find it interesting and could benefit from it. 👍

 

What's foreign? map?

 

map is just a cleaner way to do some loop operations. Whatever you return will be added to a new array.

 

// for loop
let animations = [];

for (let i = 0; i < myElements.length; i++) {
  let animaiton = gsap.to(myElemnts[i], { x: 100 });
  animations.push(animation); 
}

// forEach
let animations = [];

myElements.forEach(element => {
  let animaiton = gsap.to(element, { x: 100 });
  animations.push(animation);  
});

// map
let animations = myElements.map(element => {
  let animation = gsap.to(element, { x: 100 });
  return animation;
});

// even shorter map
let animations = myElements.map(element => gsap.to(element, { x: 100 }));

 

Would have it been clearer if I added some superfluous code?

let animations = groups.map(element => createAnimation(element));

 

1 hour ago, Carl said:

an array of functions and then looping through those functions passing in targets

 

Returning a function was all that was need for the first couple of demos in that thread. In some of the later demos, I start returning an API to handle more complicated situations, like toggling even odd.

 

API? Just a fancy of way of saying a way to interact with something.

 

let myApi = {
  foo(value) {
    console.log("FOO", value);
  },
  bar(value) {
    console.log("BAR", value)
  }
};

myApi.foo("hello"); // FOO hello
myApi.bar(34); // BAR 34

 

That's why I would highly recommend playing around with game programming to anyone who wants to get good at UI. Games are really just super complicated UIs. It forces you to rethink how you approach problems, and group related operations up into an API.

 

  • Like 2
Link to comment
Share on other sites

@OSUblake Thanks for all the extra effort and explaining. I get what map() does, but don't use it often (or at all). Probably the biggest hangup is really just the concept of returning functions. It's not something I see often in "user code" around here (or other tutorial sites) although I know it's the secret sauce to many of GSAP's features and helpers.  Thanks again. I'll be sure to keep this thread handy.

  • Like 1
Link to comment
Share on other sites

It's really no different than calling a function that returns an animation. We're just customizing it.

 

let toggle = createAnimation();

toggle();

function createAnimation() {
  let animation = gsap.to(".box", { x: 200 });
  
  return function() {
    animation.reversed(!animation.reversed());
  }
}

 

Pretty much the same thing as this.

let toggle;
createAnimation();

toggle();

function createAnimation() {
  let animation = gsap.to(".box", { x: 200 });
  
  toggle = function() {
    animation.reversed(!animation.reversed());
  }
}

 

But maybe the function closure is confusing people. I know some people think that after calling a function that everything inside just gets killed off, which it doesn't.

 

We use closures all the time, like with event listeners, but we usually don't expose the callback to the outside... or do we?

 

createAnimation();

// toggles box animation 😲
box.click();

function createAnimation() {
  let animation = gsap.to(box, { x: 200 });
  
  box.addEventListener("click", () => {
    animation.reversed(!animation.reversed());
  });
}

 

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

 

  • Like 3
Link to comment
Share on other sites

  • 9 months later...
  • 1 year later...

Hi @Rodrigo, thanks a lot :)

I guess my animation works a bit different than the main example. So i have to rebuild it like this that the first child is set to active before using the accordion. And also i try to set it that is used the "height: auto" option and not a fixed px value. This is my code:
 

let groups = gsap.utils.toArray(".accordion-group");
let menus = gsap.utils.toArray(".accordion-menu");
let menuToggles = groups.map(createAnimation);
 
menus.forEach((menu) => {
  menu.addEventListener("click", () => toggleMenu(menu));
});
 
function toggleMenu(clickedMenu) {
  menuToggles.forEach((toggleFn) => toggleFn(clickedMenu));
}
 
function createAnimation(element) {
  let menu = element.querySelector(".accordion-menu");
  let box = element.querySelector(".accordeon-item .accordeon-item__content");
  let toggleIcon = element.querySelector(".accordeon-item .accordeon-item__button .button-icon");
  gsap.set(box, { height: "auto" });
    let animation = gsap
      .timeline()
      .from(box, {
      height: 0,
      duration: 0.5,
      ease: "power1.inOut",
     onComplete: function() {
       $(toggleIcon).addClass('icon-switch-on');
       $(toggleIcon).removeClass('active');
     }
 })
.reverse();
 
return function (clickedMenu) {
  if (clickedMenu === menu) {
    animation.reversed(!animation.reversed());
    $(toggleIcon).removeClass('icon-switch-on');
    $(toggleIcon).addClass('active');
  } else {
    animation.reverse();
    $(toggleIcon).removeClass('icon-switch-on');
    $(toggleIcon).addClass('active');
  }
 };
}


Thank youu!

Link to comment
Share on other sites

Hi @Marcel93,

 

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 dependancies 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 the gsap-trial NPM package for using any of the bonus plugins: 

 

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. 

Link to comment
Share on other sites

Hi @Rodrigo,

thank you a lot :) That code is also way better! Nice work thank you :) There is one small problem left. I have different accordion groups... and of each group the first child has to be open. How can i do that?

Best,

Marcel

 

Link to comment
Share on other sites

50 minutes ago, Marcel93 said:

I have different accordion groups... and of each group the first child has to be open. How can i do that?

That's just a Javscript logic issue, not really a GSAP related one. Just run the same code for each accordion and it should be fine. Just create a different variable to store each accordion's active element, which can be done with a loop since each loop iteration will have it's own execution context:

https://www.freecodecamp.org/news/how-javascript-works-behind-the-scene-javascript-execution-context/

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

Hi @Rodrigo,

thank you! I will take a closer look on this! Very last issue:
How can i open and close the Accordion only on the title? So no click effect when i click on the child elements (Because i have a video in the accordion to click on).

Best,

Marcel

Link to comment
Share on other sites

Just move the click event to the title element instead the whole element:

items.forEach((e, i) => {
  const content = e.querySelector(".content");
  const t = gsap.to(content, {
    height: "auto",
    paused: i ? true : false
  });

  e._accordionTween = t;

  i === 0 && e.classList.toggle("active");
  
  // Get the title element
  const title = e.querySelector(".title")
  // Set the click event on the title
  title.addEventListener("click", () => {
    if (currentItem !== null) {
      items[currentItem].classList.toggle("active");
      if (currentItem === i) {
        currentItem = null;
        return t.reverse();
      }
      items[currentItem]._accordionTween.reverse();
    }
    e.classList.toggle("active");
    t.play();
    currentItem = i;
  });
});

Hopefully this helps.

Happy Tweening!

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