Jump to content
Search Community

Animation not triggering on first mouseenter

Vincentccw test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

See the Pen svBte by vincentccw (@vincentccw) on CodePen

 

This is the code I'm using:

///////////////////////////////////////////////////////////////////////////////////////

var hoverEffect = new TimelineMax();
 
$('div').on('mouseenter', function(){
hoverEffect.to($(this), .2, {top:"-=20px",ease:Quad.easeIn});
}).mouseleave(function(){
hoverEffect.to($(this), .2, {top:"+=20px",ease:Quad.easeOut});
});

///////////////////////////////////////////////////////////////////////////////////////

 

On first hover, the div straight away jump into the end and skip the animation but when hover the second and third time, it goes back to normal....

 

I also noticed that when I place my variable inside the mouseenter function it fixes the problem but is there anyway that I can place the variable outside the function?

Link to comment
Share on other sites

have you tried setting paused to true in your TimelineMax instance

 

http://api.greensock.com/js/com/greensock/core/Animation.html#paused()

 

have you tried using the same on() method for the mouseleave instead of the shorthand mouseleave() method

var hoverEffect = new TimelineMax({paused:true});
 
$('div').on('mouseenter', function(){
       hoverEffect.add( TweenMax.to($(this), 0.2, {top:"-=20px",ease:Quad.easeIn}) );
       hoverEffect.play();
}).on.('mouseleave', function(){
       hoverEffect.add( TweenMax.to((this), 0.2, {top:"+=20px",ease:Quad.easeOut}) );
       hoverEffect.play();
});

also its best to give use delegated events.. so where you have the div selector you would use one of its parents and put the div. I cant see your markup but something like this:

var hoverEffect = new TimelineMax({paused:true});
 
$('body').on('mouseenter', 'div', function(){
       hoverEffect.add( TweenMax.to($(this), 0.2, {top:"-=20px",ease:Quad.easeIn}) );
       hoverEffect.play();
}).on.('mouseleave',  'div', function(){
       hoverEffect.add( TweenMax.to($(this), 0.2, {top:"+=20px",ease:Quad.easeOut}) );
       hoverEffect.play();
});

'body' would be one of div's parents in this case

 

also maybe some of the great minds here might have a better idea about having the TimelineMax() instance outside of your event handler :)

Link to comment
Share on other sites

The first jump is because the timeline is not paused to begin with. The timelines playhead is progressing past 0, but the first hovereffect is added at 0, hence the jump. If you change it to

var hoverEffect = new TimelineMax({paused: true});

and then

hoverEffect.to($(this), .2, {top:"-=20px",ease:Quad.easeIn}).play();

in the mouseenter you'll avoid it.

 

It looks like what you need is for the div to only toggle between 2 positions right? You just need to make sure the div doesn't walk off the screen if the mouseenter and mouseleave events don't match up everytime?
 
If that's the case, I'd suggest a pattern like this instead:

$('div').on('mouseenter', function(){
  if (this.hovertween) {
    this.hovertween.play();
  } else {
    this.hovertween = TweenLite.to(this, .2, {top:"-=20px",ease:Quad.easeIn});
  }
}).on('mouseleave', function(){
  if (this.hovertween) this.hovertween.reverse();
});

No need for a variable outside the function, and you always have access to the tween on the DOM element if you need it. It should give the desired effect (unless you need the div to move -20 and back again even if the mouse just quickly passes by) and would also avoid any issues with mouseenter/mouseleave events firing multiple times.

  • Like 1
Link to comment
Share on other sites

have you tried setting paused to true in your TimelineMax instanace

 

http://api.greensock.com/js/com/greensock/core/Animation.html#paused()

 

have you tried using the same on() method for the mouseleave instead of the shorthand mouseleave() method

var hoverEffect = new TimelineMax({paused:true});
 
$('div').on('mouseenter', function(){
       hoverEffect.add( TweenMax.to($(this), 0.2, {top:"-=20px",ease:Quad.easeIn}) );
       hoverEffect.play();
}).on.('mouseleave', function(){
       hoverEffect.add( TweenMax.to((this), 0.2, {top:"+=20px",ease:Quad.easeOut}) );
       hoverEffect.play();
});

also its best to give use delegated events.. so where you have the div selector you would use one of its parents and put the div. I cant see your markup but something like this:

var hoverEffect = new TimelineMax({paused:true});
 
$('body').on('mouseenter', 'div', function(){
       hoverEffect.add( TweenMax.to($(this), 0.2, {top:"-=20px",ease:Quad.easeIn}) );
       hoverEffect.play();
}).on.('mouseleave',  'div', function(){
       hoverEffect.add( TweenMax.to($(this), 0.2, {top:"+=20px",ease:Quad.easeOut}) );
       hoverEffect.play();
});

'body' would be one of div's parents in this case

 

also maybe some of the great minds here might have a better idea about having the TimelineMax() instance outside of your event handler :)

Thanks I have try your method and it works fine, but just wondering why is delegated event anyway better than direct event? Just curious :)

Link to comment
Share on other sites

The first jump is because the timeline is not paused to begin with. The timelines playhead is progressing past 0, but the first hovereffect is added at 0, hence the jump. If you change it to

var hoverEffect = new TimelineMax({paused: true});

and then

hoverEffect.to($(this), .2, {top:"-=20px",ease:Quad.easeIn}).play();

in the mouseenter you'll avoid it.

 

It looks like what you need is for the div to only toggle between 2 positions right? You just need to make sure the div doesn't walk off the screen if the mouseenter and mouseleave events don't match up everytime?

 

If that's the case, I'd suggest a pattern like this instead:

$('div').on('mouseenter', function(){
  if (this.hovertween) {
    this.hovertween.play();
  } else {
    this.hovertween = TweenLite.to(this, .2, {top:"-=20px",ease:Quad.easeIn});
  }
}).on('mouseleave', function(){
  if (this.hovertween) this.hovertween.reverse();
});

No need for a variable outside the function, and you always have access to the tween on the DOM element if you need it. It should give the desired effect (unless you need the div to move -20 and back again even if the mouse just quickly passes by) and would also avoid any issues with mouseenter/mouseleave events firing multiple times.

Thanks for the solution, I like your pattern though, I've tried it and the animation is much smoother than using my original code. but I have one question does it only apply to tweenLite only? will it work on TimelineMax? I tried replace to TimelineMax.to but it doesnt seem to work...

Link to comment
Share on other sites

For this effect you don't need to use TimelineMax - it will work as is with either TweenLite or TweenMax though.

 

This is because TweenLite and TweenMax both have a static method .to(), so you can call e.g. TweenMax.to() and it will generate a new tween.

 

TimelineLite and TimelineMax are different, and do not have these static methods. TimelineMax.to() is not something you can call to create a tween. You need to create the timeline first before you can add tweens to it. e.g.

var tl = new TimelineMax();
tl.to(target, duration, vars);
// This is not a static method, it is a method of tl only.
// It can't ever be called as TimelineMax.to()
  • Like 1
Link to comment
Share on other sites

Thanks I have try your method and it works fine, but just wondering why is delegated event anyway better than direct event? Just curious :)

 

The delegated events work better than a normal event in that you are binding to the parent of the element you want to trigger. So instead of binding the event directly to every element that you want to listen to, ...you bind a single event handler to the parent of that element or elements, which in turn when the child is triggered, it bubbles up the DOM to the parent that has the event handler (mouseenter and mouseleave) attached to it... so the parent will listen for events happening on it’s descendants or children elements.

 

So this way when you attach the mouseenter and mouseleave events to your div's, it will have to bind events to each div. Using more memory because of multiple event handlers... verses just binding to its parent once.

 

Plus we would have to unbind event handlers to every element that is removed from the DOM, and bind event handlers for every element added to the DOM. But if we bind the event handler to the parent, any children that are added or removed will trigger just fine. Even if the children didn't exist after the DOM was ready and all event handlers were attached when the page first loaded.

 

The jQuery on() method keeps listening for changes to the DOM .. using jQuery off() method unbinds or kills the event handler that you previously attached / binded with on().

 

you can learn more about the on() / off() methods here:

 

http://api.jquery.com/on/

http://api.jquery.com/off/

 

more info on learning about events

 

http://learn.jquery.com/events/

 

does that make sense?

Link to comment
Share on other sites

 

For this effect you don't need to use TimelineMax - it will work as is with either TweenLite or TweenMax though.

 

This is because TweenLite and TweenMax both have a static method .to(), so you can call e.g. TweenMax.to() and it will generate a new tween.

 

TimelineLite and TimelineMax are different, and do not have these static methods. TimelineMax.to() is not something you can call to create a tween. You need to create the timeline first before you can add tweens to it. e.g.

var tl = new TimelineMax();
tl.to(target, duration, vars);
// This is not a static method, it is a method of tl only.
// It can't ever be called as TimelineMax.to()

Thanks a lot, I think I should stick with TweenLite and TweenMax instead of timeline for simplicity sake :-P

Link to comment
Share on other sites

The delegated events work better than a normal event in that you are binding to the parent of the element you want to trigger. So instead of binding the event directly to every element that you want to listen to, ...you bind a single event handler to the parent of that element or elements, which in turn when the child is triggered, it bubbles up the DOM to the parent that has the event handler (mouseenter and mouseleave) attached to it... so the parent will listen for events happening on it’s descendants or children elements.

 

So this way when you attach the mouseenter and mouseleave events to your div's, it will have to bind events to each div. Using more memory because of multiple event handlers... verses just binding to its parent once.

 

Plus we would have to unbind event handlers to every element that is removed from the DOM, and bind event handlers for every element added to the DOM. But if we bind the event handler to the parent, any children that are added or removed will trigger just fine. Even if the children didn't exist after the DOM was ready and all event handlers were attached when the page first loaded.

 

The jQuery on() method keeps listening for changes to the DOM .. using jQuery off() method unbinds or kills the event handler that you previously attached / binded with on().

 

you can learn more about the on() / off() methods here:

 

http://api.jquery.com/on/

http://api.jquery.com/off/

 

more info on learning about events

 

http://learn.jquery.com/events/

 

does that make sense?

Thanks for the detail explanations, I understand it now :)

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