Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
jknott

Accordion Menu / Reverse timeline doesn't seem to always reset all the way

Recommended Posts

I've been trying to create an accordion menu with GSAP.  I have 3 menus and I want the others to reverse their animations when one is clicked.  Each menu item also has a hover state.. which I think is causing my issue.

 

If you click around on the menu items quickly, then sometimes an element is left with it's old boxshadow property.

 

If you check out the codepen I think you'll get a good idea of what I'm talking about.  Is there a better way to do what I'm trying to do?

See the Pen b65004e402311a218643e81a8c27769c by jgknott (@jgknott) on CodePen

Link to comment
Share on other sites

Hi jknott  :)

 

Welcome to the GreenSock forum.

 

I didn't have time to run through all of your code, but it looks like all of your package divs have the same timeline and same mouse events so you can condense that quite a bit. You could use jQuery's each() method to create the timelines and not(this) selector to control the active one separately.

 

Your question reminded me of a recent thread that I think could be quite helpful to you. Please take a look here:

 

https://greensock.com/forums/topic/15692-mega-menu-with-gsap-issue/

 

Hopefully that gets you closer to a solution.

 

Happy tweening and welcome aboard.

:)

  • Like 4
Link to comment
Share on other sites

Hello jknott and Welcome to the GreenSock Forum!

 

So your codepen was private so i had to rebuild it to test and share it with you.

 

The example below uses a onReverseComplete callback to set() the clearProps property for box-shadow.

 

See the Pen vgOLmj?editors=0010 by jonathan (@jonathan) on CodePen

 

CSSPlugin Docs:

  • clearProps
    A comma-delimited list of property names that you want to clear from the element's "style" property when the tween completes (or use "all" to clear all properties). This can be useful if, for example, you have a class (or some other selector) that should apply certain styles to an element when the tween is over that would otherwise get overridden by the element.style-specific data that was applied during the tween. Typically you do not need to include vendor prefixes. CSSPlugin automatically applies the vendor prefix if necessary too
    //tweens 3 properties and then clears only "left" and "transform" (because "scale" affects the "transform" css property.)
    TweenLite.from(element, 5, {scale:0, left:200, backgroundColor:"red", clearProps:"scale,left"});
    
TimelineMax Docs onReverseComplete:

 

https://greensock.com/docs/#/HTML5/GSAP/TimelineMax/

 

onReverseComplete : Function - A function that should be called when the tween has reached its beginning again from the reverse direction. For example, if reverse() is called the tween will move back towards its beginning and when itstime reaches 0, onReverseComplete will be called. This can also happen if the tween is placed in a TimelineLite or TimelineMax instance that gets reversed and plays the tween backwards to (or past) the beginning.

 

set() method:

 

https://greensock.com/docs/#/HTML5/GSAP/TweenMax/set/

var select = function(s) {
    return document.querySelector(s);
  },
  selectAll = function(s) {
    return document.querySelectorAll(s);
  };
var delay = 80,
  animLength = .4;

var active = "basic",
  basic = select('#basic'),
  preferred = select('#preferred'),
  elite = select('#elite');

var basicTl = new TimelineMax({
    paused: true
  })
  .to('#basic', animLength, {
    height: 500,
    width: '+=16',
    x: '-=8',
    zIndex: 50,
    boxShadow: '2px 7px 30px 0 rgba(0,0,0,0.10)',
    backgroundColor: '#FFFFFF', 
    ease: Back.easeIn.config(.2),
    onReverseComplete: function() {
      TweenMax.set("#basic", {
        clearProps: "box-shadow"
      });
    }
  })

var prefTl = new TimelineMax({
    paused: true
  })
  .to('#preferred', animLength, {
    height: 500,
    width: '+=16',
    x: '-=8',
    zIndex: 50,
    boxShadow: '2px 7px 30px 0 rgba(0,0,0,0.10)',
    backgroundColor: '#FFFFFF',
    ease: Back.easeIn.config(.2),
    onReverseComplete: function() {
      TweenMax.set("#preferred", {
        clearProps: "box-shadow"
      });
    }
  })

var eliteTl = new TimelineMax({
    paused: true
  })
  .to('#elite', animLength, {
    height: 500,
    width: '+=16',
    x: '-=8',
    zIndex: 50,
    boxShadow: '2px 7px 30px 0 rgba(0,0,0,0.10)',
    backgroundColor: '#FFFFFF',
    onReverseComplete: function() {
      TweenMax.set("#elite", {
        clearProps: "box-shadow"
      });
    },
    ease: Back.easeIn.config(.2)
  })

basic.addEventListener('mouseover', function() {
  TweenMax.to(this, .2, {
    boxShadow: '0 0 30px 0 rgba(0,0,0,0.12)'
  })
});
basic.addEventListener('mouseout', function() {
  if (active !== "basic") {
    TweenMax.to("#basic", .2, {
      boxShadow: '0 0 6px 0 rgba(0,0,0,0.12)',
      backgroundColor: '#F9F9F9',
      onReverseComplete: function() {
        TweenMax.set("#basic", {
          clearProps: "box-shadow"
        });
      }
    })
  }
});
basic.addEventListener('click', function() {
  runSideAnimation('basic');
});

preferred.addEventListener('mouseover', function() {
  TweenMax.to(this, .2, {
    boxShadow: '0 0 30px 0 rgba(0,0,0,0.12)'
  })
});
preferred.addEventListener('mouseout', function() {
  if (active !== "preferred") {
    TweenMax.to(this, .2, {
      boxShadow: '0 0 6px 0 rgba(0,0,0,0.12)',
      backgroundColor: '#F9F9F9',
      onCompleteParams: [this],
      onReverseComplete: function(t) {
        TweenMax.set(t, {
          clearProps: "box-shadow"
        });
      }
    })
  }
});
preferred.addEventListener('click', function() {
  runSideAnimation('preferred');
});

elite.addEventListener('mouseover', function() {
  TweenMax.to(this, .2, {
    boxShadow: '0 0 30px 0 rgba(0,0,0,0.12)'
  })
});
elite.addEventListener('mouseout', function() {
  if (active !== "elite") {
    TweenMax.to(this, .2, {
      boxShadow: '0 0 6px 0 rgba(0,0,0,0.12)',
      backgroundColor: '#F9F9F9',
      onReverseComplete: function() {
        TweenMax.set("#basic", {
          clearProps: "box-shadow"
        });
      }
    })
  }
});
elite.addEventListener('click', function() {
  runSideAnimation('elite');
});

function runSideAnimation(section) {
  switch (section) {
    case "basic":
      active = "basic";
      basicTl.play();
      setTimeout(function() {
        prefTl.reverse();
        eliteTl.reverse();
      }, delay)
      break;

    case "preferred":
      active = "preferred";
      prefTl.play();
      setTimeout(function() {
        eliteTl.reverse();
        basicTl.reverse();
      }, delay)
      break;

    case "elite":
      active = "elite";
      eliteTl.play();
      setTimeout(function() {
        prefTl.reverse();
        basicTl.reverse();
      }, delay)
      break;

    default:
      basicTl.play();
      break;
  }
}

runSideAnimation();

Happy Tweening! :)

  • Like 4
Link to comment
Share on other sites

Wow, thanks for all the great responses and I really appreciate the level of detail you went into Jonathan!

I had no idea about onCompleteParams and sending that into the onReverseComplete function, that's awesome!!  I also had to clear the background-color prop, but that solved my issue.  Thank you all!

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