144
144
* immediately resulting in a DOM element that is at its final state. This final state is when the DOM element
145
145
* has no CSS transition/animation classes applied to it.
146
146
*
147
+ * <h3>CSS Staggering Animations</h3>
148
+ * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
149
+ * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be
150
+ * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
151
+ * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
152
+ * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
153
+ *
154
+ * <pre>
155
+ * .my-animation.ng-enter {
156
+ * /* standard transition code */
157
+ * }
158
+ * .my-animation.ng-enter-stagger {
159
+ * /* this will have a 100ms delay between each successive leave animation */
160
+ * -webkit-transition-delay: 0.1s;
161
+ * transition-delay: 0.1s;
162
+ *
163
+ * /* in case the stagger doesn't work then these two values
164
+ * must be set to 0 to avoid an accidental CSS inheritance */
165
+ * -webkit-transition-duration: 0s;
166
+ * transition-duration: 0s;
167
+ * }
168
+ * .my-animation.ng-enter.ng-enter-active {
169
+ * /* standard transition styles */
170
+ * }
171
+ * </pre>
172
+ *
173
+ * Staggering animations work by default in ngRepeat (so long as the CSS class is defiend). Outside of ngRepeat, to use staggering animations
174
+ * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
175
+ * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
176
+ * will also be reset if more than 10ms has passed after the last animation has been fired.
177
+ *
178
+ * The following code will issue the **ng-leave-stagger** event on the element provided:
179
+ *
180
+ * <pre>
181
+ * var kids = parent.children();
182
+ *
183
+ * $animate.leave(kids[0]); //stagger index=0
184
+ * $animate.leave(kids[1]); //stagger index=1
185
+ * $animate.leave(kids[2]); //stagger index=2
186
+ * $animate.leave(kids[3]); //stagger index=3
187
+ * $animate.leave(kids[4]); //stagger index=4
188
+ *
189
+ * $timeout(function() {
190
+ * //stagger has reset itself
191
+ * $animate.leave(kids[5]); //stagger index=0
192
+ * $animate.leave(kids[6]); //stagger index=1
193
+ * }, 100, false);
194
+ * </pre>
195
+ *
196
+ * Stagger animations are currently only supported within CSS-defined animations.
197
+ *
147
198
* <h2>JavaScript-defined Animations</h2>
148
199
* In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not
149
200
* yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module.
@@ -672,7 +723,7 @@ angular.module('ngAnimate', ['ng'])
672
723
var forEach = angular . forEach ;
673
724
674
725
// Detect proper transitionend/animationend event names.
675
- var transitionProp , transitionendEvent , animationProp , animationendEvent ;
726
+ var prefix = '' , transitionProp , transitionendEvent , animationProp , animationendEvent ;
676
727
677
728
// If unprefixed events are not supported but webkit-prefixed are, use the latter.
678
729
// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
@@ -683,6 +734,7 @@ angular.module('ngAnimate', ['ng'])
683
734
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
684
735
// therefore there is no reason to test anymore for other vendor prefixes: http://caniuse.com/#search=transition
685
736
if ( window . ontransitionend === undefined && window . onwebkittransitionend !== undefined ) {
737
+ prefix = '-webkit-' ;
686
738
transitionProp = 'WebkitTransition' ;
687
739
transitionendEvent = 'webkitTransitionEnd transitionend' ;
688
740
} else {
@@ -691,6 +743,7 @@ angular.module('ngAnimate', ['ng'])
691
743
}
692
744
693
745
if ( window . onanimationend === undefined && window . onwebkitanimationend !== undefined ) {
746
+ prefix = '-webkit-' ;
694
747
animationProp = 'WebkitAnimation' ;
695
748
animationendEvent = 'webkitAnimationEnd animationend' ;
696
749
} else {
@@ -722,6 +775,13 @@ angular.module('ngAnimate', ['ng'])
722
775
} , 10 , false ) ;
723
776
}
724
777
778
+ function applyStyle ( node , style ) {
779
+ var oldStyle = node . getAttribute ( 'style' ) || '' ;
780
+ var newStyle = ( oldStyle . length > 0 ? '; ' : '' ) + style ;
781
+ node . setAttribute ( 'style' , newStyle ) ;
782
+ return oldStyle ;
783
+ }
784
+
725
785
function getElementAnimationDetails ( element , cacheKey , onlyCheckTransition ) {
726
786
var data = cacheKey ? lookupCache [ cacheKey ] : null ;
727
787
if ( ! data ) {
@@ -751,6 +811,7 @@ angular.module('ngAnimate', ['ng'])
751
811
}
752
812
} ) ;
753
813
data = {
814
+ total : 0 ,
754
815
transitionDelay : transitionDelay ,
755
816
animationDelay : animationDelay ,
756
817
transitionDuration : transitionDuration ,
@@ -782,17 +843,32 @@ angular.module('ngAnimate', ['ng'])
782
843
}
783
844
784
845
function animate ( element , className , done ) {
785
-
786
846
var cacheKey = getCacheKey ( element ) ;
787
847
if ( getElementAnimationDetails ( element , cacheKey , true ) . transitionDuration > 0 ) {
788
848
789
849
done ( ) ;
790
850
return ;
791
851
}
792
852
853
+ var eventCacheKey = cacheKey + ' ' + className ;
854
+ var ii = lookupCache [ eventCacheKey ] ? ++ lookupCache [ eventCacheKey ] . total : 0 ;
855
+
856
+ var stagger = { } ;
857
+ if ( ii > 0 ) {
858
+ var staggerClassName = className + '-stagger' ;
859
+ var staggerCacheKey = cacheKey + ' ' + staggerClassName ;
860
+ var applyClasses = ! lookupCache [ staggerCacheKey ] ;
861
+
862
+ applyClasses && element . addClass ( staggerClassName ) ;
863
+
864
+ stagger = getElementAnimationDetails ( element , staggerCacheKey ) ;
865
+
866
+ applyClasses && element . removeClass ( staggerClassName ) ;
867
+ }
868
+
793
869
element . addClass ( className ) ;
794
870
795
- var timings = getElementAnimationDetails ( element , cacheKey + ' ' + className ) ;
871
+ var timings = getElementAnimationDetails ( element , eventCacheKey ) ;
796
872
797
873
/* there is no point in performing a reflow if the animation
798
874
timeout is empty (this would cause a flicker bug normally
@@ -815,12 +891,21 @@ angular.module('ngAnimate', ['ng'])
815
891
activeClassName += ( i > 0 ? ' ' : '' ) + klass + '-active' ;
816
892
} ) ;
817
893
818
- // This triggers a reflow which allows for the transition animation to kick in.
819
- var css3AnimationEvents = animationendEvent + ' ' + transitionendEvent ;
894
+ var formerStyle , css3AnimationEvents = animationendEvent + ' ' + transitionendEvent ;
820
895
896
+ // This triggers a reflow which allows for the transition animation to kick in.
821
897
afterReflow ( function ( ) {
822
898
if ( timings . transitionDuration > 0 ) {
823
899
node . style [ transitionProp + propertyKey ] = '' ;
900
+ if ( ii > 0 && stagger . transitionDelay > 0 && stagger . transitionDuration === 0 ) {
901
+ formerStyle = applyStyle ( node , prefix + 'transition-delay: ' +
902
+ ( ii * stagger . transitionDelay + timings . transitionDelay ) + 's' ) ;
903
+ }
904
+ }
905
+
906
+ if ( ii > 0 && stagger . animationDelay > 0 && stagger . animationDuration === 0 ) {
907
+ formerStyle = applyStyle ( node , prefix + 'animation-delay: ' +
908
+ ( ii * stagger . animationDelay + timings . animationDelay ) + 's' ) ;
824
909
}
825
910
element . addClass ( activeClassName ) ;
826
911
} ) ;
@@ -836,6 +921,11 @@ angular.module('ngAnimate', ['ng'])
836
921
element . removeClass ( className ) ;
837
922
element . removeClass ( activeClassName ) ;
838
923
element . removeData ( NG_ANIMATE_CLASS_KEY ) ;
924
+ if ( formerStyle != null ) {
925
+ formerStyle . length > 0 ?
926
+ node . setAttribute ( 'style' , formerStyle ) :
927
+ node . removeAttribute ( 'style' ) ;
928
+ }
839
929
840
930
// Only when the animation is cancelled is the done()
841
931
// function not called for this animation therefore
0 commit comments