@@ -549,7 +549,8 @@ angular.module('ngAnimate', ['ng'])
549
549
and the onComplete callback will be fired once the animation is fully complete.
550
550
*/
551
551
function performAnimation ( animationEvent , className , element , parentElement , afterElement , domOperation , doneCallback ) {
552
- var classes = ( element . attr ( 'class' ) || '' ) + ' ' + className ;
552
+ var currentClassName = element . attr ( 'class' ) || '' ;
553
+ var classes = currentClassName + ' ' + className ;
553
554
var animationLookup = ( ' ' + classes ) . replace ( / \s + / g, '.' ) ;
554
555
if ( ! parentElement ) {
555
556
parentElement = afterElement ? afterElement . parent ( ) : element . parent ( ) ;
@@ -602,21 +603,42 @@ angular.module('ngAnimate', ['ng'])
602
603
return ;
603
604
}
604
605
606
+ //this value will be searched for class-based CSS className lookup. Therefore,
607
+ //we prefix and suffix the current className value with spaces to avoid substring
608
+ //lookups of className tokens
609
+ var futureClassName = ' ' + currentClassName + ' ' ;
605
610
if ( ngAnimateState . running ) {
606
611
//if an animation is currently running on the element then lets take the steps
607
612
//to cancel that animation and fire any required callbacks
608
613
$timeout . cancel ( ngAnimateState . closeAnimationTimeout ) ;
609
614
cleanup ( element ) ;
610
615
cancelAnimations ( ngAnimateState . animations ) ;
611
- ( ngAnimateState . done || noop ) ( true ) ;
616
+
617
+ //if the class is removed during the reflow then it will revert the styles temporarily
618
+ //back to the base class CSS styling causing a jump-like effect to occur. This check
619
+ //here ensures that the domOperation is only performed after the reflow has commenced
620
+ if ( ngAnimateState . beforeComplete ) {
621
+ ( ngAnimateState . done || noop ) ( true ) ;
622
+ } else if ( isClassBased && ! ngAnimateState . structural ) {
623
+ //class-based animations will compare element className values after cancelling the
624
+ //previous animation to see if the element properties already contain the final CSS
625
+ //class and if so then the animation will be skipped. Since the domOperation will
626
+ //be performed only after the reflow is complete then our element's className value
627
+ //will be invalid. Therefore the same string manipulation that would occur within the
628
+ //DOM operation will be performed below so that the class comparison is valid...
629
+ futureClassName = ngAnimateState . event == 'removeClass' ?
630
+ futureClassName . replace ( ngAnimateState . className , '' ) :
631
+ futureClassName + ngAnimateState . className + ' ' ;
632
+ }
612
633
}
613
634
614
635
//There is no point in perform a class-based animation if the element already contains
615
636
//(on addClass) or doesn't contain (on removeClass) the className being animated.
616
637
//The reason why this is being called after the previous animations are cancelled
617
638
//is so that the CSS classes present on the element can be properly examined.
618
- if ( ( animationEvent == 'addClass' && element . hasClass ( className ) ) ||
619
- ( animationEvent == 'removeClass' && ! element . hasClass ( className ) ) ) {
639
+ var classNameToken = ' ' + className + ' ' ;
640
+ if ( ( animationEvent == 'addClass' && futureClassName . indexOf ( classNameToken ) >= 0 ) ||
641
+ ( animationEvent == 'removeClass' && futureClassName . indexOf ( classNameToken ) == - 1 ) ) {
620
642
fireDOMOperation ( ) ;
621
643
fireDoneCallbackAsync ( ) ;
622
644
return ;
@@ -628,6 +650,7 @@ angular.module('ngAnimate', ['ng'])
628
650
629
651
element . data ( NG_ANIMATE_STATE , {
630
652
running :true ,
653
+ event :animationEvent ,
631
654
className :className ,
632
655
structural :! isClassBased ,
633
656
animations :animations ,
0 commit comments