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

Synchronous Classes in ngAnimate #12248

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion angularFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ var angularFiles = {
'ngAnimate': [
'src/ngAnimate/shared.js',
'src/ngAnimate/body.js',
'src/ngAnimate/rafScheduler.js',
'src/ngAnimate/animateChildrenDirective.js',
'src/ngAnimate/animateCss.js',
'src/ngAnimate/animateCssDriver.js',
Expand Down
30 changes: 29 additions & 1 deletion src/ngAnimate/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,30 @@
"isElement": false,

"ELEMENT_NODE": false,
"COMMENT_NODE": false,
"NG_ANIMATE_CLASSNAME": false,
"NG_ANIMATE_CHILDREN_DATA": false,

"ADD_CLASS_SUFFIX": false,
"REMOVE_CLASS_SUFFIX": false,
"EVENT_CLASS_PREFIX": false,
"ACTIVE_CLASS_SUFFIX": false,

"TRANSITION_DURATION_PROP": false,
"TRANSITION_DELAY_PROP": false,
"TRANSITION_PROP": false,
"PROPERTY_KEY": false,
"DURATION_KEY": false,
"DELAY_KEY": false,
"TIMING_KEY": false,
"ANIMATION_DURATION_PROP": false,
"ANIMATION_DELAY_PROP": false,
"ANIMATION_PROP": false,
"ANIMATION_ITERATION_COUNT_KEY": false,
"SAFE_FAST_FORWARD_DURATION_VALUE": false,
"TRANSITIONEND_EVENT": false,
"ANIMATIONEND_EVENT": false,

"assertArg": false,
"isPromiseLike": false,
"mergeClasses": false,
Expand All @@ -38,6 +59,13 @@
"removeFromArray": false,
"stripCommentsFromElement": false,
"extractElementNode": false,
"getDomNode": false
"getDomNode": false,

"applyGeneratedPreparationClasses": false,
"clearGeneratedClasses": false,
"blockTransitions": false,
"blockKeyframeAnimations": false,
"applyInlineStyle": false,
"concatWithSpace": false
}
}
155 changes: 45 additions & 110 deletions src/ngAnimate/animateCss.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,63 +200,19 @@
* * `stagger` - A numeric time value representing the delay between successively animated elements
* ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
* * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
* `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
* `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
* * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
* * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
*
* @return {object} an object with start and end methods and details about the animation.
*
* * `start` - The method to start the animation. This will return a `Promise` when called.
* * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
*/

// Detect proper transitionend/animationend event names.
var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;

// If unprefixed events are not supported but webkit-prefixed are, use the latter.
// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
// Register both events in case `window.onanimationend` is not supported because of that,
// do the same for `transitionend` as Safari is likely to exhibit similar behavior.
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
// therefore there is no reason to test anymore for other vendor prefixes:
// http://caniuse.com/#search=transition
if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
CSS_PREFIX = '-webkit-';
TRANSITION_PROP = 'WebkitTransition';
TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
} else {
TRANSITION_PROP = 'transition';
TRANSITIONEND_EVENT = 'transitionend';
}

if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
CSS_PREFIX = '-webkit-';
ANIMATION_PROP = 'WebkitAnimation';
ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
} else {
ANIMATION_PROP = 'animation';
ANIMATIONEND_EVENT = 'animationend';
}

var DURATION_KEY = 'Duration';
var PROPERTY_KEY = 'Property';
var DELAY_KEY = 'Delay';
var TIMING_KEY = 'TimingFunction';
var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
var ANIMATION_PLAYSTATE_KEY = 'PlayState';
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
var CLOSING_TIME_BUFFER = 1.5;
var ONE_SECOND = 1000;
var BASE_TEN = 10;

var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;

var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;

var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
var CLOSING_TIME_BUFFER = 1.5;

var DETECT_CSS_PROPERTIES = {
transitionDuration: TRANSITION_DURATION_PROP,
Expand All @@ -274,6 +230,15 @@ var DETECT_STAGGER_CSS_PROPERTIES = {
animationDelay: ANIMATION_DELAY_PROP
};

function getCssKeyframeDurationStyle(duration) {
return [ANIMATION_DURATION_PROP, duration + 's'];
}

function getCssDelayStyle(delay, isKeyframeAnimation) {
var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
return [prop, delay + 's'];
}

function computeCssStyles($window, element, properties) {
var styles = Object.create(null);
var detectedStyles = $window.getComputedStyle(element) || {};
Expand Down Expand Up @@ -330,37 +295,6 @@ function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
return [style, value];
}

function getCssKeyframeDurationStyle(duration) {
return [ANIMATION_DURATION_PROP, duration + 's'];
}

function getCssDelayStyle(delay, isKeyframeAnimation) {
var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
return [prop, delay + 's'];
}

function blockTransitions(node, duration) {
// we use a negative delay value since it performs blocking
// yet it doesn't kill any existing transitions running on the
// same element which makes this safe for class-based animations
var value = duration ? '-' + duration + 's' : '';
applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
return [TRANSITION_DELAY_PROP, value];
}

function blockKeyframeAnimations(node, applyBlock) {
var value = applyBlock ? 'paused' : '';
var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
applyInlineStyle(node, [key, value]);
return [key, value];
}

function applyInlineStyle(node, styleTuple) {
var prop = styleTuple[0];
var value = styleTuple[1];
node.style[prop] = value;
}

function createLocalCacheLookup() {
var cache = Object.create(null);
return {
Expand Down Expand Up @@ -392,10 +326,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var gcsLookup = createLocalCacheLookup();
var gcsStaggerLookup = createLocalCacheLookup();

this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
'$document', '$sniffer', '$$rAFScheduler',
function($window, $$jqLite, $$AnimateRunner, $timeout,
$document, $sniffer, $$rAFScheduler) {
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$forceReflow', '$sniffer', '$$rAF',
function($window, $$jqLite, $$AnimateRunner, $timeout, $$forceReflow, $sniffer, $$rAF) {

var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);

Expand Down Expand Up @@ -452,28 +384,26 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
return stagger || {};
}

var bod = getDomNode($document).body;
var cancelLastRAFRequest;
var rafWaitQueue = [];
function waitUntilQuiet(callback) {
if (cancelLastRAFRequest) {
cancelLastRAFRequest(); //cancels the request
}
rafWaitQueue.push(callback);
$$rAFScheduler.waitUntilQuiet(function() {
cancelLastRAFRequest = $$rAF(function() {
cancelLastRAFRequest = null;
gcsLookup.flush();
gcsStaggerLookup.flush();

//the line below will force the browser to perform a repaint so
//that all the animated elements within the animation frame will
//be properly updated and drawn on screen. This is required to
//ensure that the preparation animation is properly flushed so that
//the active state picks up from there. DO NOT REMOVE THIS LINE.
//DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
//WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
//WILL TAKE YEARS AWAY FROM YOUR LIFE.
var width = bod.offsetWidth + 1;
// DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
// PLEASE EXAMINE THE `$$forceReflow` service to understand why.
var pageWidth = $$forceReflow();

// we use a for loop to ensure that if the queue is changed
// during this looping then it will consider new requests
for (var i = 0; i < rafWaitQueue.length; i++) {
rafWaitQueue[i](width);
rafWaitQueue[i](pageWidth);
}
rafWaitQueue.length = 0;
});
Expand Down Expand Up @@ -529,20 +459,20 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var addRemoveClassName = '';

if (isStructural) {
structuralClassName = pendClasses(method, 'ng-', true);
structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true);
} else if (method) {
structuralClassName = method;
}

if (options.addClass) {
addRemoveClassName += pendClasses(options.addClass, '-add');
addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX);
}

if (options.removeClass) {
if (addRemoveClassName.length) {
addRemoveClassName += ' ';
}
addRemoveClassName += pendClasses(options.removeClass, '-remove');
addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX);
}

// there may be a situation where a structural animation is combined together
Expand All @@ -556,9 +486,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
addRemoveClassName = '';
}

var setupClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
var fullClassName = classes + ' ' + setupClasses;
var activeClasses = pendClasses(setupClasses, '-active');
var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
var fullClassName = classes + ' ' + preparationClasses;
var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;

Expand All @@ -567,7 +497,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
// unless there a is raw keyframe value that is applied to the element.
if (!containsKeyframeAnimation
&& !hasToStyles
&& !setupClasses) {
&& !preparationClasses) {
return closeAndReturnNoopAnimator();
}

Expand All @@ -582,10 +512,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
};
} else {
cacheKey = gcsHashFn(node, fullClassName);
stagger = computeCachedCssStaggerStyles(node, setupClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
}

$$jqLite.addClass(element, setupClasses);
if (!options.$$skipPreparationClasses) {
$$jqLite.addClass(element, preparationClasses);
}

var applyOnlyDuration;

Expand Down Expand Up @@ -624,7 +556,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
// transition delay to allow for the transition to naturally do it's thing. The beauty here is
// that if there is no transition defined then nothing will happen and this will also allow
// other transitions to be stacked on top of each other without any chopping them out.
if (isFirst) {
if (isFirst && !options.skipBlocking) {
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
}

Expand Down Expand Up @@ -683,12 +615,13 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}

applyAnimationFromStyles(element, options);
if (!flags.blockTransition) {

if (flags.blockTransition || flags.blockKeyframeAnimation) {
applyBlocking(maxDuration);
} else if (!options.skipBlocking) {
blockTransitions(node, false);
}

applyBlocking(maxDuration);

// TODO(matsko): for 1.5 change this code to have an animator object for better debugging
return {
$$willAnimate: true,
Expand Down Expand Up @@ -730,7 +663,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
animationClosed = true;
animationPaused = false;

$$jqLite.removeClass(element, setupClasses);
if (!options.$$skipPreparationClasses) {
$$jqLite.removeClass(element, preparationClasses);
}
$$jqLite.removeClass(element, activeClasses);

blockKeyframeAnimations(node, false);
Expand Down Expand Up @@ -857,7 +792,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
$$jqLite.addClass(element, activeClasses);

if (flags.recalculateTimingStyles) {
fullClassName = node.className + ' ' + setupClasses;
fullClassName = node.className + ' ' + preparationClasses;
cacheKey = gcsHashFn(node, fullClassName);

timings = computeTimings(node, fullClassName, cacheKey);
Expand Down
Loading