Skip to content

Commit 5d59dee

Browse files
committed
fix($animate): use $timeout to handle the delay within staggering animations
When transition-delay and animation-delay were used to drive the staggering animation the result was unpredictable at times due to the browser not being able to register the generated delay styles in time. This caused a hard to track down bug that didn't have a solid solution when styles were being used. This fix ensures that stagger delays are handled by the $timeout service. Closes angular#7228 Closes angular#7547 Closes angular#8297 Closes angular#8547 BREAKING CHANGE If any stagger code consisted of having BOTH transition staggers and delay staggers together then that will not work the same way. Angular will no instead choose the highest stagger delay value and set the timeout to wait for that before applying the active CSS class.
1 parent 7805a0c commit 5d59dee

File tree

2 files changed

+195
-122
lines changed

2 files changed

+195
-122
lines changed

src/ngAnimate/animate.js

+64-70
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,7 @@ angular.module('ngAnimate', ['ng'])
13651365
var PROPERTY_KEY = 'Property';
13661366
var DELAY_KEY = 'Delay';
13671367
var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
1368+
var ANIMATION_PLAYSTATE_KEY = 'PlayState';
13681369
var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
13691370
var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
13701371
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
@@ -1436,47 +1437,32 @@ angular.module('ngAnimate', ['ng'])
14361437
var transitionDelay = 0;
14371438
var animationDuration = 0;
14381439
var animationDelay = 0;
1439-
var transitionDelayStyle;
1440-
var animationDelayStyle;
1441-
var transitionDurationStyle;
1442-
var transitionPropertyStyle;
14431440

14441441
//we want all the styles defined before and after
14451442
forEach(element, function(element) {
14461443
if (element.nodeType == ELEMENT_NODE) {
14471444
var elementStyles = $window.getComputedStyle(element) || {};
14481445

1449-
transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
1450-
1446+
var transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
14511447
transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
14521448

1453-
transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY];
1454-
1455-
transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
1456-
1449+
var transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
14571450
transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
14581451

1459-
animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
1460-
1461-
animationDelay = Math.max(parseMaxTime(animationDelayStyle), animationDelay);
1452+
var animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
1453+
animationDelay = Math.max(parseMaxTime(elementStyles[ANIMATION_PROP + DELAY_KEY]), animationDelay);
14621454

14631455
var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]);
1464-
14651456
if(aDuration > 0) {
14661457
aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1;
14671458
}
1468-
14691459
animationDuration = Math.max(aDuration, animationDuration);
14701460
}
14711461
});
14721462
data = {
14731463
total : 0,
1474-
transitionPropertyStyle: transitionPropertyStyle,
1475-
transitionDurationStyle: transitionDurationStyle,
1476-
transitionDelayStyle: transitionDelayStyle,
14771464
transitionDelay: transitionDelay,
14781465
transitionDuration: transitionDuration,
1479-
animationDelayStyle: animationDelayStyle,
14801466
animationDelay: animationDelay,
14811467
animationDuration: animationDuration
14821468
};
@@ -1552,18 +1538,17 @@ angular.module('ngAnimate', ['ng'])
15521538
running : formerData.running || 0,
15531539
itemIndex : itemIndex,
15541540
blockTransition : blockTransition,
1555-
blockAnimation : blockAnimation,
15561541
closeAnimationFns : closeAnimationFns
15571542
});
15581543

15591544
var node = extractElementNode(element);
15601545

15611546
if(blockTransition) {
1562-
node.style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
1547+
blockTransitions(node, true);
15631548
}
15641549

15651550
if(blockAnimation) {
1566-
node.style[ANIMATION_PROP] = 'none 0s';
1551+
blockAnimations(node, true);
15671552
}
15681553

15691554
return true;
@@ -1578,22 +1563,42 @@ angular.module('ngAnimate', ['ng'])
15781563
}
15791564

15801565
if(elementData.blockTransition) {
1581-
node.style[TRANSITION_PROP + PROPERTY_KEY] = '';
1582-
}
1583-
1584-
if(elementData.blockAnimation) {
1585-
node.style[ANIMATION_PROP] = '';
1566+
blockTransitions(node, false);
15861567
}
15871568

15881569
var activeClassName = '';
1570+
var pendingClassName = '';
15891571
forEach(className.split(' '), function(klass, i) {
1590-
activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
1572+
var prefix = (i > 0 ? ' ' : '') + klass;
1573+
activeClassName += prefix + '-active';
1574+
pendingClassName += prefix + '-pending';
15911575
});
15921576

1593-
element.addClass(activeClassName);
1577+
var style = '', appliedStyles = [];
1578+
var itemIndex = elementData.itemIndex;
1579+
var stagger = elementData.stagger;
1580+
var staggerTime = 0;
1581+
if(itemIndex > 0) {
1582+
var transitionStaggerDelay = 0;
1583+
if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
1584+
transitionStaggerDelay = stagger.transitionDelay * itemIndex;
1585+
}
1586+
1587+
var animationStaggerDelay = 0;
1588+
if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
1589+
animationStaggerDelay = stagger.animationDelay * itemIndex;
1590+
appliedStyles.push(CSS_PREFIX + 'animation-play-state');
1591+
}
1592+
1593+
staggerTime = Math.round(Math.max(transitionStaggerDelay, animationStaggerDelay) * 100) / 100;
1594+
}
1595+
1596+
if (!staggerTime) {
1597+
element.addClass(activeClassName);
1598+
}
1599+
15941600
var eventCacheKey = elementData.cacheKey + ' ' + activeClassName;
15951601
var timings = getElementAnimationDetails(element, eventCacheKey);
1596-
15971602
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
15981603
if(maxDuration === 0) {
15991604
element.removeClass(activeClassName);
@@ -1603,57 +1608,43 @@ angular.module('ngAnimate', ['ng'])
16031608
}
16041609

16051610
var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
1606-
var stagger = elementData.stagger;
1607-
var itemIndex = elementData.itemIndex;
16081611
var maxDelayTime = maxDelay * ONE_SECOND;
16091612

1610-
var style = '', appliedStyles = [];
1611-
if(timings.transitionDuration > 0) {
1612-
var propertyStyle = timings.transitionPropertyStyle;
1613-
if(propertyStyle.indexOf('all') == -1) {
1614-
style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';';
1615-
style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ';';
1616-
appliedStyles.push(CSS_PREFIX + 'transition-property');
1617-
appliedStyles.push(CSS_PREFIX + 'transition-duration');
1618-
}
1619-
}
1620-
1621-
if(itemIndex > 0) {
1622-
if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
1623-
var delayStyle = timings.transitionDelayStyle;
1624-
style += CSS_PREFIX + 'transition-delay: ' +
1625-
prepareStaggerDelay(delayStyle, stagger.transitionDelay, itemIndex) + '; ';
1626-
appliedStyles.push(CSS_PREFIX + 'transition-delay');
1627-
}
1628-
1629-
if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
1630-
style += CSS_PREFIX + 'animation-delay: ' +
1631-
prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, itemIndex) + '; ';
1632-
appliedStyles.push(CSS_PREFIX + 'animation-delay');
1633-
}
1634-
}
1635-
16361613
if(appliedStyles.length > 0) {
16371614
//the element being animated may sometimes contain comment nodes in
16381615
//the jqLite object, so we're safe to use a single variable to house
16391616
//the styles since there is always only one element being animated
16401617
var oldStyle = node.getAttribute('style') || '';
1641-
node.setAttribute('style', oldStyle + '; ' + style);
1618+
if (oldStyle.charAt(oldStyle.length-1) !== ';') {
1619+
oldStyle += ';';
1620+
}
1621+
node.setAttribute('style', oldStyle + ' ' + style);
16421622
}
16431623

16441624
var startTime = Date.now();
16451625
var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
1626+
var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
1627+
var totalTime = (staggerTime + animationTime) * ONE_SECOND;
1628+
1629+
var staggerTimeout;
1630+
if(staggerTime > 0) {
1631+
element.addClass(pendingClassName);
1632+
staggerTimeout = $timeout(function() {
1633+
staggerTimeout = null;
1634+
element.addClass(activeClassName);
1635+
element.removeClass(pendingClassName);
1636+
if (timings.animationDuration > 0) {
1637+
blockAnimations(node, false);
1638+
}
1639+
}, staggerTime * ONE_SECOND, false);
1640+
}
16461641

16471642
element.on(css3AnimationEvents, onAnimationProgress);
16481643
elementData.closeAnimationFns.push(function() {
16491644
onEnd();
16501645
activeAnimationComplete();
16511646
});
16521647

1653-
var staggerTime = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
1654-
var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
1655-
var totalTime = (staggerTime + animationTime) * ONE_SECOND;
1656-
16571648
elementData.running++;
16581649
animationCloseHandler(element, totalTime);
16591650
return onEnd;
@@ -1664,6 +1655,10 @@ angular.module('ngAnimate', ['ng'])
16641655
function onEnd(cancelled) {
16651656
element.off(css3AnimationEvents, onAnimationProgress);
16661657
element.removeClass(activeClassName);
1658+
element.removeClass(pendingClassName);
1659+
if (staggerTimeout) {
1660+
$timeout.cancel(staggerTimeout);
1661+
}
16671662
animateClose(element, className);
16681663
var node = extractElementNode(element);
16691664
for (var i in appliedStyles) {
@@ -1693,13 +1688,12 @@ angular.module('ngAnimate', ['ng'])
16931688
}
16941689
}
16951690

1696-
function prepareStaggerDelay(delayStyle, staggerDelay, index) {
1697-
var style = '';
1698-
forEach(delayStyle.split(','), function(val, i) {
1699-
style += (i > 0 ? ',' : '') +
1700-
(index * staggerDelay + parseInt(val, 10)) + 's';
1701-
});
1702-
return style;
1691+
function blockTransitions(node, bool) {
1692+
node.style[TRANSITION_PROP + PROPERTY_KEY] = bool ? 'none' : '';
1693+
}
1694+
1695+
function blockAnimations(node, bool) {
1696+
node.style[ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY] = bool ? 'paused' : '';
17031697
}
17041698

17051699
function animateBefore(animationEvent, element, className, calculationDecorator) {

0 commit comments

Comments
 (0)