Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 54637a3

Browse files
committedDec 19, 2013
fix($animate): use a scheduled timeout in favor of a fallback property to close transitions
With ngAnimate, CSS transitions, that are not properlty triggered, are forceably closed off by appling a fallback property. The fallback property approach works, however, its styling itself may effect CSS inheritance or cause the element to render improperly. Therefore, its best to stick to using a scheduled timeout to run sometime after the highest animation time has passed. Closes #5255 Closes #5241 Closes #5405
1 parent 277a5ea commit 54637a3

File tree

3 files changed

+128
-171
lines changed

3 files changed

+128
-171
lines changed
 

‎css/angular.css

-11
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,3 @@
99
ng\:form {
1010
display: block;
1111
}
12-
13-
/* The styles below ensure that the CSS transition will ALWAYS
14-
* animate and close. A nasty bug occurs with CSS transitions where
15-
* when the active class isn't set, or if the active class doesn't
16-
* contain any styles to transition to, then, if ngAnimate is used,
17-
* it will appear as if the webpage is broken due to the forever hanging
18-
* animations. The border-spacing (!ie) and zoom (ie) CSS properties are
19-
* used below since they trigger a transition without making the browser
20-
* animate anything and they're both highly underused CSS properties */
21-
.ng-animate-start { border-spacing:1px 1px; -ms-zoom:1.0001; }
22-
.ng-animate-active { border-spacing:0px 0px; -ms-zoom:1; }

‎src/ngAnimate/animate.js

+76-34
Original file line numberDiff line numberDiff line change
@@ -881,27 +881,73 @@ angular.module('ngAnimate', ['ng'])
881881
var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
882882
var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
883883
var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
884-
var NG_ANIMATE_FALLBACK_CLASS_NAME = 'ng-animate-start';
885-
var NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME = 'ng-animate-active';
886884
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
885+
var CLOSING_TIME_BUFFER = 1.5;
886+
var ONE_SECOND = 1000;
887887

888+
var animationCounter = 0;
888889
var lookupCache = {};
889890
var parentCounter = 0;
891+
var animationReflowQueue = [];
892+
var animationElementQueue = [];
893+
var animationTimer;
894+
var closingAnimationTime = 0;
895+
var timeOut = false;
896+
function afterReflow(element, callback) {
897+
$timeout.cancel(animationTimer);
890898

891-
var animationReflowQueue = [], animationTimer, timeOut = false;
892-
function afterReflow(callback) {
893899
animationReflowQueue.push(callback);
894-
$timeout.cancel(animationTimer);
900+
901+
var node = extractElementNode(element);
902+
element = angular.element(node);
903+
animationElementQueue.push(element);
904+
905+
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
906+
closingAnimationTime = Math.max(closingAnimationTime,
907+
(elementData.maxDelay + elementData.maxDuration) * CLOSING_TIME_BUFFER * ONE_SECOND);
908+
909+
//by placing a counter we can avoid an accidental
910+
//race condition which may close an animation when
911+
//a follow-up animation is midway in its animation
912+
elementData.animationCount = animationCounter;
913+
895914
animationTimer = $timeout(function() {
896915
forEach(animationReflowQueue, function(fn) {
897916
fn();
898917
});
918+
919+
//copy the list of elements so that successive
920+
//animations won't conflict if they're added before
921+
//the closing animation timeout has run
922+
var elementQueueSnapshot = [];
923+
var animationCounterSnapshot = animationCounter;
924+
forEach(animationElementQueue, function(elm) {
925+
elementQueueSnapshot.push(elm);
926+
});
927+
928+
$timeout(function() {
929+
closeAllAnimations(elementQueueSnapshot, animationCounterSnapshot);
930+
elementQueueSnapshot = null;
931+
}, closingAnimationTime, false);
932+
899933
animationReflowQueue = [];
934+
animationElementQueue = [];
900935
animationTimer = null;
901936
lookupCache = {};
937+
closingAnimationTime = 0;
938+
animationCounter++;
902939
}, 10, false);
903940
}
904941

942+
function closeAllAnimations(elements, count) {
943+
forEach(elements, function(element) {
944+
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
945+
if(elementData && elementData.animationCount == count) {
946+
(elementData.closeAnimationFn || noop)();
947+
}
948+
});
949+
}
950+
905951
function getElementAnimationDetails(element, cacheKey) {
906952
var data = cacheKey ? lookupCache[cacheKey] : null;
907953
if(!data) {
@@ -1007,6 +1053,7 @@ angular.module('ngAnimate', ['ng'])
10071053
timeout is empty (this would cause a flicker bug normally
10081054
in the page. There is also no point in performing an animation
10091055
that only has a delay and no duration */
1056+
var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
10101057
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
10111058
if(maxDuration === 0) {
10121059
element.removeClass(className);
@@ -1016,13 +1063,9 @@ angular.module('ngAnimate', ['ng'])
10161063
//temporarily disable the transition so that the enter styles
10171064
//don't animate twice (this is here to avoid a bug in Chrome/FF).
10181065
var activeClassName = '';
1019-
if(timings.transitionDuration > 0) {
1020-
element.addClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
1021-
activeClassName += NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME + ' ';
1022-
blockTransitions(element);
1023-
} else {
1066+
timings.transitionDuration > 0 ?
1067+
blockTransitions(element) :
10241068
blockKeyframeAnimations(element);
1025-
}
10261069

10271070
forEach(className.split(' '), function(klass, i) {
10281071
activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
@@ -1032,6 +1075,7 @@ angular.module('ngAnimate', ['ng'])
10321075
className : className,
10331076
activeClassName : activeClassName,
10341077
maxDuration : maxDuration,
1078+
maxDelay : maxDelay,
10351079
classes : className + ' ' + activeClassName,
10361080
timings : timings,
10371081
stagger : stagger,
@@ -1066,30 +1110,28 @@ angular.module('ngAnimate', ['ng'])
10661110
}
10671111

10681112
function animateRun(element, className, activeAnimationComplete) {
1069-
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
1113+
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
10701114
var node = extractElementNode(element);
1071-
if(node.className.indexOf(className) == -1 || !data) {
1115+
if(node.className.indexOf(className) == -1 || !elementData) {
10721116
activeAnimationComplete();
10731117
return;
10741118
}
10751119

1076-
var timings = data.timings;
1077-
var stagger = data.stagger;
1078-
var maxDuration = data.maxDuration;
1079-
var activeClassName = data.activeClassName;
1080-
var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000;
1120+
var timings = elementData.timings;
1121+
var stagger = elementData.stagger;
1122+
var maxDuration = elementData.maxDuration;
1123+
var activeClassName = elementData.activeClassName;
1124+
var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * ONE_SECOND;
10811125
var startTime = Date.now();
10821126
var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
1083-
var ii = data.ii;
1127+
var ii = elementData.ii;
10841128

1085-
var applyFallbackStyle, style = '', appliedStyles = [];
1129+
var style = '', appliedStyles = [];
10861130
if(timings.transitionDuration > 0) {
10871131
var propertyStyle = timings.transitionPropertyStyle;
10881132
if(propertyStyle.indexOf('all') == -1) {
1089-
applyFallbackStyle = true;
1090-
var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'border-spacing';
1091-
style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ', ' + fallbackProperty + '; ';
1092-
style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ', ' + timings.transitionDuration + 's; ';
1133+
style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';';
1134+
style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + 's;';
10931135
appliedStyles.push(CSS_PREFIX + 'transition-property');
10941136
appliedStyles.push(CSS_PREFIX + 'transition-duration');
10951137
}
@@ -1098,10 +1140,6 @@ angular.module('ngAnimate', ['ng'])
10981140
if(ii > 0) {
10991141
if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
11001142
var delayStyle = timings.transitionDelayStyle;
1101-
if(applyFallbackStyle) {
1102-
delayStyle += ', ' + timings.transitionDelay + 's';
1103-
}
1104-
11051143
style += CSS_PREFIX + 'transition-delay: ' +
11061144
prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; ';
11071145
appliedStyles.push(CSS_PREFIX + 'transition-delay');
@@ -1124,19 +1162,24 @@ angular.module('ngAnimate', ['ng'])
11241162

11251163
element.on(css3AnimationEvents, onAnimationProgress);
11261164
element.addClass(activeClassName);
1165+
elementData.closeAnimationFn = function() {
1166+
onEnd();
1167+
activeAnimationComplete();
1168+
};
1169+
return onEnd;
11271170

11281171
// This will automatically be called by $animate so
11291172
// there is no need to attach this internally to the
11301173
// timeout done method.
1131-
return function onEnd(cancelled) {
1174+
function onEnd(cancelled) {
11321175
element.off(css3AnimationEvents, onAnimationProgress);
11331176
element.removeClass(activeClassName);
11341177
animateClose(element, className);
11351178
var node = extractElementNode(element);
11361179
for (var i in appliedStyles) {
11371180
node.style.removeProperty(appliedStyles[i]);
11381181
}
1139-
};
1182+
}
11401183

11411184
function onAnimationProgress(event) {
11421185
event.stopPropagation();
@@ -1202,7 +1245,7 @@ angular.module('ngAnimate', ['ng'])
12021245
//data from the element which will not make the 2nd animation
12031246
//happen in the first place
12041247
var cancel = preReflowCancellation;
1205-
afterReflow(function() {
1248+
afterReflow(element, function() {
12061249
unblockTransitions(element);
12071250
unblockKeyframeAnimations(element);
12081251
//once the reflow is complete then we point cancel to
@@ -1218,7 +1261,6 @@ angular.module('ngAnimate', ['ng'])
12181261

12191262
function animateClose(element, className) {
12201263
element.removeClass(className);
1221-
element.removeClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
12221264
element.removeData(NG_ANIMATE_CSS_DATA_KEY);
12231265
}
12241266

@@ -1268,7 +1310,7 @@ angular.module('ngAnimate', ['ng'])
12681310
beforeAddClass : function(element, className, animationCompleted) {
12691311
var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'));
12701312
if(cancellationMethod) {
1271-
afterReflow(function() {
1313+
afterReflow(element, function() {
12721314
unblockTransitions(element);
12731315
unblockKeyframeAnimations(element);
12741316
animationCompleted();
@@ -1285,7 +1327,7 @@ angular.module('ngAnimate', ['ng'])
12851327
beforeRemoveClass : function(element, className, animationCompleted) {
12861328
var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'));
12871329
if(cancellationMethod) {
1288-
afterReflow(function() {
1330+
afterReflow(element, function() {
12891331
unblockTransitions(element);
12901332
unblockKeyframeAnimations(element);
12911333
animationCompleted();

2 commit comments

Comments
 (2)

marcfallows commented on Jul 9, 2014

@marcfallows
Member

@matsko Not sure if this checkin is the culprit, but your "Rolling out animations with your own directives" example no longer works in 1.2.6+ (but works if I switch the version to 1.2.5). This is the only $animate commit so I'm thinking this could be the cause.
http://plnkr.co/edit/gaoKAVZWeCF5elBdVYjI?p=preview

It works on the first step, but not the subsequent steps (only between step 0 and step 1)

matsko commented on Jul 14, 2014

@matsko
ContributorAuthor

@marcfallows looks like 1.2 is failing and 1.3. is fine. I will take a look at it.

This repository has been archived.