Skip to content

Commit 0b4a8ea

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#7547 Closes angular#7228 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 1f527d9 commit 0b4a8ea

File tree

2 files changed

+175
-77
lines changed

2 files changed

+175
-77
lines changed

src/ngAnimate/animate.js

+42-25
Original file line numberDiff line numberDiff line change
@@ -1586,14 +1586,15 @@ angular.module('ngAnimate', ['ng'])
15861586
}
15871587

15881588
var activeClassName = '';
1589+
var pendingClassName = '';
15891590
forEach(className.split(' '), function(klass, i) {
1590-
activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
1591+
var prefix = (i > 0 ? ' ' : '') + klass;
1592+
activeClassName += prefix + '-active';
1593+
pendingClassName += prefix + '-pending';
15911594
});
15921595

1593-
element.addClass(activeClassName);
15941596
var eventCacheKey = elementData.cacheKey + ' ' + activeClassName;
15951597
var timings = getElementAnimationDetails(element, eventCacheKey);
1596-
15971598
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
15981599
if(maxDuration === 0) {
15991600
element.removeClass(activeClassName);
@@ -1603,8 +1604,6 @@ angular.module('ngAnimate', ['ng'])
16031604
}
16041605

16051606
var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
1606-
var stagger = elementData.stagger;
1607-
var itemIndex = elementData.itemIndex;
16081607
var maxDelayTime = maxDelay * ONE_SECOND;
16091608

16101609
var style = '', appliedStyles = [];
@@ -1618,19 +1617,31 @@ angular.module('ngAnimate', ['ng'])
16181617
}
16191618
}
16201619

1620+
var itemIndex = elementData.itemIndex;
1621+
var stagger = elementData.stagger;
1622+
1623+
var staggerStyle, staggerTime = 0;
16211624
if(itemIndex > 0) {
1625+
var transitionStaggerDelay = 0;
16221626
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+
transitionStaggerDelay = stagger.transitionDelay * itemIndex;
16271628
}
16281629

1630+
var animationStaggerDelay = 0;
16291631
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');
1632+
animationStaggerDelay = stagger.animationDelay * itemIndex;
1633+
1634+
staggerStyle = CSS_PREFIX + 'animation-play-state';
1635+
appliedStyles.push(staggerStyle);
1636+
1637+
style += staggerStyle + ':paused;';
16331638
}
1639+
1640+
staggerTime = Math.round(Math.max(transitionStaggerDelay, animationStaggerDelay) * 100) / 100;
1641+
}
1642+
1643+
if (!staggerTime) {
1644+
element.addClass(activeClassName);
16341645
}
16351646

16361647
if(appliedStyles.length > 0) {
@@ -1643,17 +1654,28 @@ angular.module('ngAnimate', ['ng'])
16431654

16441655
var startTime = Date.now();
16451656
var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
1657+
var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
1658+
var totalTime = (staggerTime + animationTime) * ONE_SECOND;
1659+
1660+
var staggerTimer;
1661+
if(staggerTime > 0) {
1662+
element.addClass(pendingClassName);
1663+
staggerTimer = $timeout(function() {
1664+
staggerTimer = null;
1665+
element.addClass(activeClassName);
1666+
element.removeClass(pendingClassName);
1667+
if (staggerStyle) {
1668+
element.css(staggerStyle, '');
1669+
}
1670+
}, staggerTime * ONE_SECOND, false);
1671+
}
16461672

16471673
element.on(css3AnimationEvents, onAnimationProgress);
16481674
elementData.closeAnimationFns.push(function() {
16491675
onEnd();
16501676
activeAnimationComplete();
16511677
});
16521678

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-
16571679
elementData.running++;
16581680
animationCloseHandler(element, totalTime);
16591681
return onEnd;
@@ -1664,6 +1686,10 @@ angular.module('ngAnimate', ['ng'])
16641686
function onEnd(cancelled) {
16651687
element.off(css3AnimationEvents, onAnimationProgress);
16661688
element.removeClass(activeClassName);
1689+
element.removeClass(pendingClassName);
1690+
if (staggerTimer) {
1691+
$timeout.cancel(staggerTimer);
1692+
}
16671693
animateClose(element, className);
16681694
var node = extractElementNode(element);
16691695
for (var i in appliedStyles) {
@@ -1693,15 +1719,6 @@ angular.module('ngAnimate', ['ng'])
16931719
}
16941720
}
16951721

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;
1703-
}
1704-
17051722
function animateBefore(animationEvent, element, className, calculationDecorator) {
17061723
if(animateSetup(animationEvent, element, className, calculationDecorator)) {
17071724
return function(cancelled) {

0 commit comments

Comments
 (0)