aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMatias Niemelä2013-08-21 15:03:41 -0400
committerMisko Hevery2013-08-23 14:04:35 -0700
commitee2f3d21da6c9fccfe1e6a4ea8a65627519c8bf2 (patch)
treefae875a458eb632e583871ea7c35963c5b624f68 /src
parentfb3a7db0809b959d50be4cb93a65a91200071dd5 (diff)
downloadangular.js-ee2f3d21da6c9fccfe1e6a4ea8a65627519c8bf2.tar.bz2
fix($animate): only execute a timeout when transitions or keyframe animations are used
ngAnimate causes a 1ms flicker on the screen when no CSS animations are present on the element. The solution is to change $animate to only use $timeouts when a duration is found on the element before the transition/keyframe animation takes over. Closes #3613
Diffstat (limited to 'src')
-rw-r--r--src/ngAnimate/animate.js149
1 files changed, 77 insertions, 72 deletions
diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js
index 4207470b..1c1a0cba 100644
--- a/src/ngAnimate/animate.js
+++ b/src/ngAnimate/animate.js
@@ -282,7 +282,9 @@ angular.module('ngAnimate', ['ng'])
*/
enter : function(element, parent, after, done) {
$delegate.enter(element, parent, after);
- performAnimation('enter', 'ng-enter', element, parent, after, done);
+ performAnimation('enter', 'ng-enter', element, parent, after, function() {
+ $timeout(done || noop, 0, false);
+ });
},
/**
@@ -350,7 +352,9 @@ angular.module('ngAnimate', ['ng'])
*/
move : function(element, parent, after, done) {
$delegate.move(element, parent, after);
- performAnimation('move', 'ng-move', element, null, null, done);
+ performAnimation('move', 'ng-move', element, null, null, function() {
+ $timeout(done || noop, 0, false);
+ });
},
/**
@@ -361,7 +365,8 @@ angular.module('ngAnimate', ['ng'])
* @description
* Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class.
* Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide
- * the animate service the setup and active CSS classes in order to trigger the animation.
+ * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions
+ * or keyframes are defined on the -add CSS class).
*
* Below is a breakdown of each step that occurs during addClass animation:
*
@@ -395,7 +400,8 @@ angular.module('ngAnimate', ['ng'])
* @description
* Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value
* from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in
- * order to provide the animate service the setup and active CSS classes in order to trigger the animation.
+ * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if
+ * no CSS transitions or keyframes are defined on the -remove CSS class).
*
* Below is a breakdown of each step that occurs during removeClass animation:
*
@@ -546,90 +552,89 @@ angular.module('ngAnimate', ['ng'])
function animate(element, className, done) {
if (!($sniffer.transitions || $sniffer.animations)) {
done();
- } else {
- var activeClassName = '';
- $timeout(startAnimation, 1, false);
-
- //this acts as the cancellation function in case
- //a new animation is triggered while another animation
- //is still going on (otherwise the active className
- //would still hang around until the timer is complete).
- return onEnd;
- }
-
- function parseMaxTime(str) {
- var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : [];
- forEach(values, function(value) {
- total = Math.max(parseFloat(value) || 0, total);
- });
- return total;
+ return;
}
- function startAnimation() {
- var duration = 0;
- forEach(className.split(' '), function(klass, i) {
- activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
- });
+ //one day all browsers will have these properties
+ var w3cAnimationProp = 'animation';
+ var w3cTransitionProp = 'transition';
- element.addClass(activeClassName);
+ //but some still use vendor-prefixed styles
+ var vendorAnimationProp = $sniffer.vendorPrefix + 'Animation';
+ var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition';
- //one day all browsers will have these properties
- var w3cAnimationProp = 'animation';
- var w3cTransitionProp = 'transition';
+ var durationKey = 'Duration',
+ delayKey = 'Delay',
+ animationIterationCountKey = 'IterationCount';
- //but some still use vendor-prefixed styles
- var vendorAnimationProp = $sniffer.vendorPrefix + 'Animation';
- var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition';
+ //we want all the styles defined before and after
+ var duration = 0, ELEMENT_NODE = 1;
+ forEach(element, function(element) {
+ if (element.nodeType == ELEMENT_NODE) {
+ var elementStyles = $window.getComputedStyle(element) || {};
- var durationKey = 'Duration',
- delayKey = 'Delay',
- animationIterationCountKey = 'IterationCount';
+ var transitionDelay = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + delayKey]),
+ parseMaxTime(elementStyles[vendorTransitionProp + delayKey]));
- //we want all the styles defined before and after
- var ELEMENT_NODE = 1;
- forEach(element, function(element) {
- if (element.nodeType == ELEMENT_NODE) {
- var elementStyles = $window.getComputedStyle(element) || {};
+ var animationDelay = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + delayKey]),
+ parseMaxTime(elementStyles[vendorAnimationProp + delayKey]));
- var transitionDelay = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + delayKey]),
- parseMaxTime(elementStyles[vendorTransitionProp + delayKey]));
+ var transitionDuration = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + durationKey]),
+ parseMaxTime(elementStyles[vendorTransitionProp + durationKey]));
- var animationDelay = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + delayKey]),
- parseMaxTime(elementStyles[vendorAnimationProp + delayKey]));
+ var animationDuration = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + durationKey]),
+ parseMaxTime(elementStyles[vendorAnimationProp + durationKey]));
- var transitionDuration = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + durationKey]),
- parseMaxTime(elementStyles[vendorTransitionProp + durationKey]));
-
- var animationDuration = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + durationKey]),
- parseMaxTime(elementStyles[vendorAnimationProp + durationKey]));
+ if(animationDuration > 0) {
+ animationDuration *= Math.max(parseInt(elementStyles[w3cAnimationProp + animationIterationCountKey]) || 0,
+ parseInt(elementStyles[vendorAnimationProp + animationIterationCountKey]) || 0,
+ 1);
+ }
- if(animationDuration > 0) {
- animationDuration *= Math.max(parseInt(elementStyles[w3cAnimationProp + animationIterationCountKey]) || 0,
- parseInt(elementStyles[vendorAnimationProp + animationIterationCountKey]) || 0,
- 1);
- }
+ duration = Math.max(animationDelay + animationDuration,
+ transitionDelay + transitionDuration,
+ duration);
+ }
+ });
- duration = Math.max(animationDelay + animationDuration,
- transitionDelay + transitionDuration,
- duration);
- }
+ /* there is no point in performing a reflow if the animation
+ timeout is empty (this would cause a flicker bug normally
+ in the page */
+ if(duration > 0) {
+ var activeClassName = '';
+ forEach(className.split(' '), function(klass, i) {
+ activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
});
- $timeout(done, duration * 1000, false);
+ $timeout(function() {
+ element.addClass(activeClassName);
+ $timeout(done, duration * 1000, false);
+ },0,false);
+
+ //this will automatically be called by $animate so
+ //there is no need to attach this internally to the
+ //timeout done method
+ return function onEnd(cancelled) {
+ element.removeClass(activeClassName);
+
+ //only when the animation is cancelled is the done()
+ //function not called for this animation therefore
+ //this must be also called
+ if(cancelled) {
+ done();
+ }
+ }
+ }
+ else {
+ done();
}
- //this will automatically be called by $animate so
- //there is no need to attach this internally to the
- //timeout done method
- function onEnd(cancelled) {
- element.removeClass(activeClassName);
-
- //only when the animation is cancelled is the done()
- //function not called for this animation therefore
- //this must be also called
- if(cancelled) {
- done();
- }
+ function parseMaxTime(str) {
+ var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : [];
+ forEach(values, function(value) {
+ total = Math.max(parseFloat(value) || 0, total);
+ });
+ return total;
}
}