Jump to content
GreenSock

failure13

Very old className bug

Go to solution Solved by GreenSock,

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

Here's reduced example, just try to hover or activate tabs and surely it will break (either no active class will be applied, or multiple ones simultaneously).

 

And then you can just comment GSAP's js - it will work.

Happens only with className, doesn't matter actually in which circumstances, Bootstrap or vanilla js, using addClass or classList to apply active class - doesn't really matter, they just collide with Greensock's className.

 

Have seen this bug ALOT over past years, but always thought that it will get fixed by magic with newer versions...

 

I think it's time to fix this guys)

See the Pen jWRrob by failure13 (@failure13) on CodePen

Link to comment
Share on other sites

We can't address bugs that aren't reported.

 

In this case it appears className is working as designed.

 

two things of note in the screen grab:

 

  1. the hover class is being ADDED to the element so the element has .active AND .hover applied
  2. the active class is overwriting the hover class (they both are trying to control the same property)

taELsMP.png

 

see this reduced test case of what happens when 2 classes that set the same properties are applied to the same element with no js: http://codepen.io/GreenSock/pen/pgBEee

 

in your case you probably need to tween from active to hover, not add hover with "+=hover" or perhaps not have the border in the active class.

http://codepen.io/GreenSock/pen/VeNKpg

  • Like 2
Link to comment
Share on other sites

 

in your case you probably need to tween from active to hover, not add hover with "+=hover" or perhaps not have the border in the active class.

 

I can't do that, for obvious reasons

See the Pen YwMGYb by failure13 (@failure13) on CodePen

1. If you have a lot of styles in realcase framework + custom code scenario (unlike this case), just "hover" applies not all of them.

2. Actually it doesn't really work in my example with hover and active states

 

Leaving border ain't really an options since i'm talking about a lot of stuff in actual real-life classes)(

 

Any suggestions?

 

P.S. I perfectly understand that they collide and stuff, i could even understand why by design className can have multiple active classes at once, but when it comes to delete both hover and active classes - i'm out, that seems like bug, not feature to me

Link to comment
Share on other sites

Thanks for creating the codepen demo. Very helpful. 

 

I think I know what's going on and as odd as it may sound, I don't think it's a bug in GSAP. Or perhaps I'm misunderstanding, but allow me to explain my train of thought...

 

The issue seems to happen when you've got a tween happening WHILE you click on that same element and then 3rd party code alters the classes on that same element (bootstrap in this case). A className tween tells GSAP to tween from a particular class (or set of classes) to another. Period. So let's say you start a hover tween that tweens from className "" (empty) to "hover". GSAP must record both of those values and make sure they're applied properly during the tween. Great. Now let's say while that hover tween is happening, you click and bootstrap chucks another class into the mix ("active"). GSAP has no way of knowing that, and it continues doing its job of animating from "" to "hover". See the issue? 

 

Think of it kinda like if you were tweening the color white to black using GSAP, but then halfway through that tween, you manually set that property to red, like element.style.color = "red". You might see that for a split second, until the next tick when GSAP sets the value to gray and continues on toward black. 

 

That's not a bug. 

 

The only ways around that would be:

  1. Set that color through GSAP, like TweenLite.set(element, {color:"red"}) so that its overwriting algorithm kicks in and stops the original tween. 
  2. Or GSAP would have to constantly monitor every value to watch for outside changes which would exact a HUGE performance penalty. GSAP is highly optimized for performance, so it rips through its rendering tasks very quickly. If it had to say "wait, are you still gray? Doh! Someone set you to red...let me recalculate everything and go from red to black now", that'd really slow things down. Plus if you reverse() that tween it wouldn't be able to go from black to red to white because a single tween cannot have a variable number of states (a tween goes from one value to another, so basically 2 states - beginning and end).

Does that clear things up at all? 

 

Frankly, I'm not a big fan of className tweens for 2 reasons:

  1. They require looping through every property to see where any changes are, and then isolating those. Not ideal from a performance standpoint (but it's unavoidable in JS). It's much, much faster to just define the properties you want to animate.
  2. If you're just swapping classes and want them to transition, CSS transitions are entirely adequate and likely faster. They fit into the CSS-centric mindset which is where most people who do className tweens tend to live. 

Thanks again for bringing this up after it's been gnawing at you for so long so that we can explore it. 

  • Like 4
Link to comment
Share on other sites

My two cents if i may Good Gentlemen ^_^

 

I totally agree with Jack with being against className tweens, for the very reasons Jack described above. It is better just to tween as usual and not switch classes.

 

className animation is only good for CSS transitions, since CSS transitions always trigger based on the addition and removal of a class name, as well as any pseudo class states you have with changing CSS values.

 

Also i see no reason to add a hover className like Carl advised above, since you can just use the :hover pseudo-class instead. And just like Carl advised above you can only have one property be applied to a DOM element. Even if you have multiple CSS rules fighting to apply a specific property. That is just the way CSS works! You cant have 2 of the same property applied to the same element from 2 or more separate CSS rules. Which ever CSS rule has the highest specificity will win and their property will be used.

 

https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity

 

:)

  • Like 4
Link to comment
Share on other sites

Great explanation, tight community and all.

Though i've read it a lot already through different other topics and understand why it happens, there's really no point to show css specifity for me...

 

I still have question Jack: why and how can it be that *empty* sometimes can prioritize over any js addClass, classList (active, hover and other possible additional class), only when using gsap className in food-chain?

 

And still it's a shame.

I can't agree with option 1, and certainly not with option 2, perfomance is must.)

 

Because it's huge part of my workflow, i don't want to use css animations at all, they're more buggy than anything you've ever done + i have to consider all crossbrowser crap and write same line multiple times (preprocessors won't help you to save this space, since they'll output final css anyway) and most of all - i want to abstract how it looks from how it behave i guess you can't really argue on why i want to do so)

 

Ok...Let me try to ask differently, is there any way to reliably prioritize js active over js hover? (ex 0 timeouts or stuff like that).

Link to comment
Share on other sites

  • Solution

Hm, I didn't quite understand your first question ("why and how can it be that *empty* sometimes can prioritize over any js addClass, classList (active, hover and other possible additional class), only when using gsap className in food-chain?"). Are you asking why the tween would overwrite the list of classes? If so, it's because it must do so to honor your instructions. When you set a property to tween, the engine must make sure it's setting that value over the course of the tween so that, for example, if that animation was in a timeline and then much later (after the animation finished and you altered that property in other ways), if you seek() to a certain spot in that animation or reverse() it or whatever, the values would display properly. If we didn't do that, you'd get weird behavior in certain scenarios where an animation just doesn't seem to work properly after the first time it runs. 

 

Let's say you start with class="" and tween to class="hover", then that tween finishes, and you manually set class="active" and then tell that first tween to progress(0.5). You shouldn't see "active" in the list of classes because the tween needs to set it to what it's supposed to be during that tween ("hover" in this case). Again, that's not a bug at all - it's the correct behavior even though I can see why you'd think it's a problem in your scenario. 

 

You asked if there's a reliable way to prioritize js "active" over "hover" - sure, that was my recommendation #1. Just set() it through GSAP instead of having bootstrap or other framework do it. Or you can just call TweenLite.killTweensOf(element) before you set the class to active. Does that answer your question?

  • Like 4
Link to comment
Share on other sites

 

You asked if there's a reliable way to prioritize js "active" over "hover" - sure, that was my recommendation #1. Just set() it through GSAP instead of having bootstrap or other framework do it. Or you can just call TweenLite.killTweensOf(element) before you set the class to active. Does that answer your question?

 

Yeah i guess it's an answer, thx, just hoped there would be some easier quickfix for now, since i have pretty many projects right now, which based on ny wip css-js framework based on Bootstrap (haven't messed with js too much now, but i'll sure as hell rewrite everything with gsap-integration in mind).

 

And i guess i understood about empty also, just thought at the first place that if you for example do:

1. addClass('hover') and addClass('active') simultaniously (it will sure as hell at least pick one of them and won't leave just empty class, i ain't even talking here about visual part, just classes applied)

 

is kinda equal to

 

2. GSAP className: '+=hover' and addClass('active')

 

But thinking about seek(), reverse() and other cool stuff like that - it's kinda logical it's not same as first scenario, definately.

 

DOM-browser rendering game ain't changing at all since XX century :/

We're doomed

 

P.S. Or you can just call TweenLite.killTweensOf(element) before you set the class to active. - this ain't working in my case though)

Link to comment
Share on other sites

Regarding prioritizing active over hover classname.. that goes back to CSS Specifity. You would need to make the CSS rule with the active classname be more specific. Then active classname would override the hover classname rule.

 

That's why Jack recommended using the GSAP set() method.. since using set() would apply inline styles and those inline styles would override the active and hover classname regardless of CSS Specifity. Due to the way CSS works.. inline styles are more specific than external CSS in a stylesheet. which goes back full circle to CSS Specifity :)

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