mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-01-12 22:45:52 +08:00
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 #7228 Closes #7547 Closes #8297 Closes #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 now instead choose the highest stagger delay value and set the timeout to wait for that before applying the active CSS class.
This commit is contained in:
@@ -484,12 +484,12 @@ angular.module('ngAnimate', ['ng'])
|
||||
// the matching CSS class.
|
||||
if (status < 0) {
|
||||
//does it have the class or will it have the class
|
||||
if(hasClass || matchingAnimation.event == 'addClass') {
|
||||
if (hasClass || matchingAnimation.event == 'addClass') {
|
||||
toRemove.push(className);
|
||||
}
|
||||
} else if (status > 0) {
|
||||
//is the class missing or will it be removed?
|
||||
if(!hasClass || matchingAnimation.event == 'removeClass') {
|
||||
if (!hasClass || matchingAnimation.event == 'removeClass') {
|
||||
toAdd.push(className);
|
||||
}
|
||||
}
|
||||
@@ -544,7 +544,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
if (!classNameAdd) {
|
||||
className = classNameRemove;
|
||||
animationEvent = 'removeClass';
|
||||
} else if(!classNameRemove) {
|
||||
} else if (!classNameRemove) {
|
||||
className = classNameAdd;
|
||||
animationEvent = 'addClass';
|
||||
} else {
|
||||
@@ -1101,6 +1101,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
var totalActiveAnimations = ngAnimateState.totalActive || 0;
|
||||
var lastAnimation = ngAnimateState.last;
|
||||
var skipAnimation = false;
|
||||
|
||||
if (totalActiveAnimations > 0) {
|
||||
var animationsToCancel = [];
|
||||
if (!runner.isClassBased) {
|
||||
@@ -1384,6 +1385,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
var PROPERTY_KEY = 'Property';
|
||||
var DELAY_KEY = 'Delay';
|
||||
var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
|
||||
var ANIMATION_PLAYSTATE_KEY = 'PlayState';
|
||||
var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
|
||||
var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
|
||||
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
|
||||
@@ -1455,47 +1457,33 @@ angular.module('ngAnimate', ['ng'])
|
||||
var transitionDelay = 0;
|
||||
var animationDuration = 0;
|
||||
var animationDelay = 0;
|
||||
var transitionDelayStyle;
|
||||
var animationDelayStyle;
|
||||
var transitionDurationStyle;
|
||||
var transitionPropertyStyle;
|
||||
|
||||
//we want all the styles defined before and after
|
||||
forEach(element, function(element) {
|
||||
if (element.nodeType == ELEMENT_NODE) {
|
||||
var elementStyles = $window.getComputedStyle(element) || {};
|
||||
|
||||
transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
|
||||
|
||||
var transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
|
||||
transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
|
||||
|
||||
transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY];
|
||||
|
||||
transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
|
||||
|
||||
var transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
|
||||
transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
|
||||
|
||||
animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
|
||||
|
||||
animationDelay = Math.max(parseMaxTime(animationDelayStyle), animationDelay);
|
||||
var animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
|
||||
animationDelay = Math.max(parseMaxTime(elementStyles[ANIMATION_PROP + DELAY_KEY]), animationDelay);
|
||||
|
||||
var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]);
|
||||
|
||||
if (aDuration > 0) {
|
||||
aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1;
|
||||
}
|
||||
|
||||
animationDuration = Math.max(aDuration, animationDuration);
|
||||
}
|
||||
});
|
||||
data = {
|
||||
total : 0,
|
||||
transitionPropertyStyle: transitionPropertyStyle,
|
||||
transitionDurationStyle: transitionDurationStyle,
|
||||
transitionDelayStyle: transitionDelayStyle,
|
||||
transitionDelay: transitionDelay,
|
||||
transitionDuration: transitionDuration,
|
||||
animationDelayStyle: animationDelayStyle,
|
||||
animationDelay: animationDelay,
|
||||
animationDuration: animationDuration
|
||||
};
|
||||
@@ -1571,18 +1559,17 @@ angular.module('ngAnimate', ['ng'])
|
||||
running : formerData.running || 0,
|
||||
itemIndex : itemIndex,
|
||||
blockTransition : blockTransition,
|
||||
blockAnimation : blockAnimation,
|
||||
closeAnimationFns : closeAnimationFns
|
||||
});
|
||||
|
||||
var node = extractElementNode(element);
|
||||
|
||||
if (blockTransition) {
|
||||
node.style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
|
||||
blockTransitions(node, true);
|
||||
}
|
||||
|
||||
if (blockAnimation) {
|
||||
node.style[ANIMATION_PROP] = 'none 0s';
|
||||
blockAnimations(node, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1597,22 +1584,43 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
if (elementData.blockTransition) {
|
||||
node.style[TRANSITION_PROP + PROPERTY_KEY] = '';
|
||||
}
|
||||
|
||||
if (elementData.blockAnimation) {
|
||||
node.style[ANIMATION_PROP] = '';
|
||||
blockTransitions(node, false);
|
||||
}
|
||||
|
||||
var activeClassName = '';
|
||||
var pendingClassName = '';
|
||||
forEach(className.split(' '), function(klass, i) {
|
||||
activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
|
||||
var prefix = (i > 0 ? ' ' : '') + klass;
|
||||
activeClassName += prefix + '-active';
|
||||
pendingClassName += prefix + '-pending';
|
||||
});
|
||||
|
||||
element.addClass(activeClassName);
|
||||
var style = '';
|
||||
var appliedStyles = [];
|
||||
var itemIndex = elementData.itemIndex;
|
||||
var stagger = elementData.stagger;
|
||||
var staggerTime = 0;
|
||||
if (itemIndex > 0) {
|
||||
var transitionStaggerDelay = 0;
|
||||
if (stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
|
||||
transitionStaggerDelay = stagger.transitionDelay * itemIndex;
|
||||
}
|
||||
|
||||
var animationStaggerDelay = 0;
|
||||
if (stagger.animationDelay > 0 && stagger.animationDuration === 0) {
|
||||
animationStaggerDelay = stagger.animationDelay * itemIndex;
|
||||
appliedStyles.push(CSS_PREFIX + 'animation-play-state');
|
||||
}
|
||||
|
||||
staggerTime = Math.round(Math.max(transitionStaggerDelay, animationStaggerDelay) * 100) / 100;
|
||||
}
|
||||
|
||||
if (!staggerTime) {
|
||||
element.addClass(activeClassName);
|
||||
}
|
||||
|
||||
var eventCacheKey = elementData.cacheKey + ' ' + activeClassName;
|
||||
var timings = getElementAnimationDetails(element, eventCacheKey);
|
||||
|
||||
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
|
||||
if (maxDuration === 0) {
|
||||
element.removeClass(activeClassName);
|
||||
@@ -1622,46 +1630,36 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
|
||||
var stagger = elementData.stagger;
|
||||
var itemIndex = elementData.itemIndex;
|
||||
var maxDelayTime = maxDelay * ONE_SECOND;
|
||||
|
||||
var style = '', appliedStyles = [];
|
||||
if (timings.transitionDuration > 0) {
|
||||
var propertyStyle = timings.transitionPropertyStyle;
|
||||
if (propertyStyle.indexOf('all') == -1) {
|
||||
style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';';
|
||||
style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ';';
|
||||
appliedStyles.push(CSS_PREFIX + 'transition-property');
|
||||
appliedStyles.push(CSS_PREFIX + 'transition-duration');
|
||||
}
|
||||
}
|
||||
|
||||
if (itemIndex > 0) {
|
||||
if (stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
|
||||
var delayStyle = timings.transitionDelayStyle;
|
||||
style += CSS_PREFIX + 'transition-delay: ' +
|
||||
prepareStaggerDelay(delayStyle, stagger.transitionDelay, itemIndex) + '; ';
|
||||
appliedStyles.push(CSS_PREFIX + 'transition-delay');
|
||||
}
|
||||
|
||||
if (stagger.animationDelay > 0 && stagger.animationDuration === 0) {
|
||||
style += CSS_PREFIX + 'animation-delay: ' +
|
||||
prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, itemIndex) + '; ';
|
||||
appliedStyles.push(CSS_PREFIX + 'animation-delay');
|
||||
}
|
||||
}
|
||||
|
||||
if (appliedStyles.length > 0) {
|
||||
//the element being animated may sometimes contain comment nodes in
|
||||
//the jqLite object, so we're safe to use a single variable to house
|
||||
//the styles since there is always only one element being animated
|
||||
var oldStyle = node.getAttribute('style') || '';
|
||||
node.setAttribute('style', oldStyle + '; ' + style);
|
||||
if (oldStyle.charAt(oldStyle.length-1) !== ';') {
|
||||
oldStyle += ';';
|
||||
}
|
||||
node.setAttribute('style', oldStyle + ' ' + style);
|
||||
}
|
||||
|
||||
var startTime = Date.now();
|
||||
var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
|
||||
var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
|
||||
var totalTime = (staggerTime + animationTime) * ONE_SECOND;
|
||||
|
||||
var staggerTimeout;
|
||||
if (staggerTime > 0) {
|
||||
element.addClass(pendingClassName);
|
||||
staggerTimeout = $timeout(function() {
|
||||
staggerTimeout = null;
|
||||
element.addClass(activeClassName);
|
||||
element.removeClass(pendingClassName);
|
||||
if (timings.animationDuration > 0) {
|
||||
blockAnimations(node, false);
|
||||
}
|
||||
}, staggerTime * ONE_SECOND, false);
|
||||
}
|
||||
|
||||
element.on(css3AnimationEvents, onAnimationProgress);
|
||||
elementData.closeAnimationFns.push(function() {
|
||||
@@ -1669,10 +1667,6 @@ angular.module('ngAnimate', ['ng'])
|
||||
activeAnimationComplete();
|
||||
});
|
||||
|
||||
var staggerTime = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
|
||||
var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
|
||||
var totalTime = (staggerTime + animationTime) * ONE_SECOND;
|
||||
|
||||
elementData.running++;
|
||||
animationCloseHandler(element, totalTime);
|
||||
return onEnd;
|
||||
@@ -1683,6 +1677,10 @@ angular.module('ngAnimate', ['ng'])
|
||||
function onEnd(cancelled) {
|
||||
element.off(css3AnimationEvents, onAnimationProgress);
|
||||
element.removeClass(activeClassName);
|
||||
element.removeClass(pendingClassName);
|
||||
if (staggerTimeout) {
|
||||
$timeout.cancel(staggerTimeout);
|
||||
}
|
||||
animateClose(element, className);
|
||||
var node = extractElementNode(element);
|
||||
for (var i in appliedStyles) {
|
||||
@@ -1712,13 +1710,12 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
}
|
||||
|
||||
function prepareStaggerDelay(delayStyle, staggerDelay, index) {
|
||||
var style = '';
|
||||
forEach(delayStyle.split(','), function(val, i) {
|
||||
style += (i > 0 ? ',' : '') +
|
||||
(index * staggerDelay + parseInt(val, 10)) + 's';
|
||||
});
|
||||
return style;
|
||||
function blockTransitions(node, bool) {
|
||||
node.style[TRANSITION_PROP + PROPERTY_KEY] = bool ? 'none' : '';
|
||||
}
|
||||
|
||||
function blockAnimations(node, bool) {
|
||||
node.style[ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY] = bool ? 'paused' : '';
|
||||
}
|
||||
|
||||
function animateBefore(animationEvent, element, className, calculationDecorator) {
|
||||
|
||||
@@ -5,6 +5,17 @@ describe("ngAnimate", function() {
|
||||
beforeEach(module('ngAnimate'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
|
||||
function getMaxValue(prop, element, $window) {
|
||||
var node = element[0];
|
||||
var cs = $window.getComputedStyle(node);
|
||||
var prop0 = 'webkit' + prop.charAt(0).toUpperCase() + prop.substr(1);
|
||||
var values = (cs[prop0] || cs[prop]).split(/\s*,\s*/);
|
||||
var maxDelay = 0;
|
||||
forEach(values, function(value) {
|
||||
maxDelay = Math.max(parseFloat(value) || 0, maxDelay);
|
||||
});
|
||||
return maxDelay;
|
||||
}
|
||||
|
||||
it("should disable animations on bootstrap for structural animations even after the first digest has passed", function() {
|
||||
var hasBeenAnimated = false;
|
||||
@@ -1161,7 +1172,7 @@ describe("ngAnimate", function() {
|
||||
);
|
||||
|
||||
|
||||
it("should stagger the items when the correct CSS class is provided",
|
||||
it("should pause the playstate when performing a stagger animation",
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) {
|
||||
|
||||
if(!$sniffer.animations) return;
|
||||
@@ -1198,10 +1209,9 @@ describe("ngAnimate", function() {
|
||||
$animate.triggerReflow();
|
||||
|
||||
expect(elements[0].attr('style')).toBeFalsy();
|
||||
expect(elements[1].attr('style')).toMatch(/animation-delay: 0\.1\d*s/);
|
||||
expect(elements[2].attr('style')).toMatch(/animation-delay: 0\.2\d*s/);
|
||||
expect(elements[3].attr('style')).toMatch(/animation-delay: 0\.3\d*s/);
|
||||
expect(elements[4].attr('style')).toMatch(/animation-delay: 0\.4\d*s/);
|
||||
for(i = 1; i < 5; i++) {
|
||||
expect(elements[i].attr('style')).toMatch(/animation-play-state:\s*paused/);
|
||||
}
|
||||
|
||||
//final closing timeout
|
||||
$timeout.flush();
|
||||
@@ -1220,10 +1230,9 @@ describe("ngAnimate", function() {
|
||||
$timeout.verifyNoPendingTasks();
|
||||
|
||||
expect(elements[0].attr('style')).toBeFalsy();
|
||||
expect(elements[1].attr('style')).not.toMatch(/animation-delay: 0\.1\d*s/);
|
||||
expect(elements[2].attr('style')).not.toMatch(/animation-delay: 0\.2\d*s/);
|
||||
expect(elements[3].attr('style')).not.toMatch(/animation-delay: 0\.3\d*s/);
|
||||
expect(elements[4].attr('style')).not.toMatch(/animation-delay: 0\.4\d*s/);
|
||||
for(i=1;i<5;i++) {
|
||||
expect(elements[i].attr('style')).not.toMatch(/animation-play-state:\s*paused/);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -1255,23 +1264,26 @@ describe("ngAnimate", function() {
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elements[0].attr('style')).toBeUndefined();
|
||||
expect(elements[1].attr('style')).toMatch(/animation:.*?none/);
|
||||
expect(elements[2].attr('style')).toMatch(/animation:.*?none/);
|
||||
expect(elements[3].attr('style')).toMatch(/animation:.*?none/);
|
||||
for(i = 1; i < 4; i++) {
|
||||
expect(elements[i].attr('style')).toMatch(/animation-play-state:\s*paused/);
|
||||
}
|
||||
|
||||
$animate.triggerReflow();
|
||||
|
||||
expect(elements[0].attr('style')).toBeUndefined();
|
||||
expect(elements[1].attr('style')).not.toMatch(/animation:.*?none/);
|
||||
expect(elements[1].attr('style')).toMatch(/animation-delay: 0.2\d*s/);
|
||||
expect(elements[2].attr('style')).not.toMatch(/animation:.*?none/);
|
||||
expect(elements[2].attr('style')).toMatch(/animation-delay: 0.4\d*s/);
|
||||
expect(elements[3].attr('style')).not.toMatch(/animation:.*?none/);
|
||||
expect(elements[3].attr('style')).toMatch(/animation-delay: 0.6\d*s/);
|
||||
for(i = 1; i < 4; i++) {
|
||||
expect(elements[i].attr('style')).toMatch(/animation-play-state:\s*paused/);
|
||||
}
|
||||
|
||||
$timeout.flush(800);
|
||||
|
||||
for(i = 1; i < 4; i++) {
|
||||
expect(elements[i].attr('style')).not.toMatch(/animation-play-state/);
|
||||
}
|
||||
}));
|
||||
|
||||
it("should stagger items when multiple animation durations/delays are defined",
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) {
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement, $window) {
|
||||
|
||||
if(!$sniffer.transitions) return;
|
||||
|
||||
@@ -1298,10 +1310,19 @@ describe("ngAnimate", function() {
|
||||
$rootScope.$digest();
|
||||
$animate.triggerReflow();
|
||||
|
||||
expect(elements[0].attr('style')).toBeFalsy();
|
||||
expect(elements[1].attr('style')).toMatch(/animation-delay: 1\.1\d*s,\s*2\.1\d*s/);
|
||||
expect(elements[2].attr('style')).toMatch(/animation-delay: 1\.2\d*s,\s*2\.2\d*s/);
|
||||
expect(elements[3].attr('style')).toMatch(/animation-delay: 1\.3\d*s,\s*2\.3\d*s/);
|
||||
for(i = 1; i < 4; i++) {
|
||||
expect(elements[i]).not.toHaveClass('ng-enter-active');
|
||||
expect(elements[i]).toHaveClass('ng-enter-pending');
|
||||
expect(getMaxValue('animationDelay', elements[i], $window)).toBe(2);
|
||||
}
|
||||
|
||||
$timeout.flush(300);
|
||||
|
||||
for(i = 1; i < 4; i++) {
|
||||
expect(elements[i]).toHaveClass('ng-enter-active');
|
||||
expect(elements[i]).not.toHaveClass('ng-enter-pending');
|
||||
expect(getMaxValue('animationDelay', elements[i], $window)).toBe(2);
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
||||
@@ -1577,7 +1598,7 @@ describe("ngAnimate", function() {
|
||||
}));
|
||||
|
||||
it("should stagger the items when the correct CSS class is provided",
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) {
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement, $browser) {
|
||||
|
||||
if(!$sniffer.transitions) return;
|
||||
|
||||
@@ -1612,11 +1633,8 @@ describe("ngAnimate", function() {
|
||||
$rootScope.$digest();
|
||||
$animate.triggerReflow();
|
||||
|
||||
expect(elements[0].attr('style')).toBeFalsy();
|
||||
expect(elements[1].attr('style')).toMatch(/transition-delay: 0\.1\d*s/);
|
||||
expect(elements[2].attr('style')).toMatch(/transition-delay: 0\.2\d*s/);
|
||||
expect(elements[3].attr('style')).toMatch(/transition-delay: 0\.3\d*s/);
|
||||
expect(elements[4].attr('style')).toMatch(/transition-delay: 0\.4\d*s/);
|
||||
expect($browser.deferredFns.length).toEqual(5); //4 staggers + 1 combined timeout
|
||||
$timeout.flush();
|
||||
|
||||
for(i = 0; i < 5; i++) {
|
||||
dealoc(elements[i]);
|
||||
@@ -1629,16 +1647,12 @@ describe("ngAnimate", function() {
|
||||
$rootScope.$digest();
|
||||
$animate.triggerReflow();
|
||||
|
||||
expect(elements[0].attr('style')).toBeFalsy();
|
||||
expect(elements[1].attr('style')).not.toMatch(/transition-delay: 0\.1\d*s/);
|
||||
expect(elements[2].attr('style')).not.toMatch(/transition-delay: 0\.2\d*s/);
|
||||
expect(elements[3].attr('style')).not.toMatch(/transition-delay: 0\.3\d*s/);
|
||||
expect(elements[4].attr('style')).not.toMatch(/transition-delay: 0\.4\d*s/);
|
||||
expect($browser.deferredFns.length).toEqual(0); //no animation was triggered
|
||||
}));
|
||||
|
||||
|
||||
it("should stagger items when multiple transition durations/delays are defined",
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) {
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement, $window) {
|
||||
|
||||
if(!$sniffer.transitions) return;
|
||||
|
||||
@@ -1665,11 +1679,19 @@ describe("ngAnimate", function() {
|
||||
$rootScope.$digest();
|
||||
$animate.triggerReflow();
|
||||
|
||||
expect(elements[0].attr('style')).toMatch(/transition-duration: 1\d*s,\s*3\d*s;/);
|
||||
expect(elements[0].attr('style')).not.toContain('transition-delay');
|
||||
expect(elements[1].attr('style')).toMatch(/transition-delay: 2\.1\d*s,\s*4\.1\d*s/);
|
||||
expect(elements[2].attr('style')).toMatch(/transition-delay: 2\.2\d*s,\s*4\.2\d*s/);
|
||||
expect(elements[3].attr('style')).toMatch(/transition-delay: 2\.3\d*s,\s*4\.3\d*s/);
|
||||
for(i = 1; i < 4; i++) {
|
||||
expect(elements[i]).not.toHaveClass('ng-enter-active');
|
||||
expect(elements[i]).toHaveClass('ng-enter-pending');
|
||||
expect(getMaxValue('transitionDelay', elements[i], $window)).toBe(4);
|
||||
}
|
||||
|
||||
$timeout.flush(300);
|
||||
|
||||
for(i = 1; i < 4; i++) {
|
||||
expect(elements[i]).toHaveClass('ng-enter-active');
|
||||
expect(elements[i]).not.toHaveClass('ng-enter-pending');
|
||||
expect(getMaxValue('transitionDelay', elements[i], $window)).toBe(4);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -1811,20 +1833,22 @@ describe("ngAnimate", function() {
|
||||
$animate.triggerReflow(); //reflow
|
||||
expect(element.children().length).toBe(5);
|
||||
|
||||
for(i = 0; i < 5; i++) {
|
||||
expect(kids[i].hasClass('ng-enter-active')).toBe(true);
|
||||
for(i = 1; i < 5; i++) {
|
||||
expect(kids[i]).not.toHaveClass('ng-enter-active');
|
||||
expect(kids[i]).toHaveClass('ng-enter-pending');
|
||||
}
|
||||
|
||||
$timeout.flush(7500);
|
||||
$timeout.flush(2000);
|
||||
|
||||
for(i = 0; i < 5; i++) {
|
||||
expect(kids[i].hasClass('ng-enter-active')).toBe(true);
|
||||
for(i = 1; i < 5; i++) {
|
||||
expect(kids[i]).toHaveClass('ng-enter-active');
|
||||
expect(kids[i]).not.toHaveClass('ng-enter-pending');
|
||||
}
|
||||
|
||||
//(stagger * index) + (duration + delay) * 150%
|
||||
//0.5 * 4 + 5 * 1.5 = 9500;
|
||||
//9500 - 7500 = 2000
|
||||
$timeout.flush(1999); //remove 1999 more
|
||||
//9500 - 2000 - 7499 = 1
|
||||
$timeout.flush(7499);
|
||||
|
||||
for(i = 0; i < 5; i++) {
|
||||
expect(kids[i].hasClass('ng-enter-active')).toBe(true);
|
||||
@@ -1837,6 +1861,52 @@ describe("ngAnimate", function() {
|
||||
}
|
||||
}));
|
||||
|
||||
it("should cancel all the existing stagger timers when the animation is cancelled",
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $browser) {
|
||||
|
||||
if (!$sniffer.transitions) return;
|
||||
|
||||
ss.addRule('.entering-element.ng-enter',
|
||||
'-webkit-transition:5s linear all;' +
|
||||
'transition:5s linear all;');
|
||||
|
||||
ss.addRule('.entering-element.ng-enter-stagger',
|
||||
'-webkit-transition-delay:1s;' +
|
||||
'transition-delay:1s;');
|
||||
|
||||
var cancellations = [];
|
||||
element = $compile(html('<div></div>'))($rootScope);
|
||||
var kids = [];
|
||||
for(var i = 0; i < 5; i++) {
|
||||
kids.push(angular.element('<div class="entering-element"></div>'));
|
||||
cancellations.push($animate.enter(kids[i], element));
|
||||
}
|
||||
$rootScope.$digest();
|
||||
|
||||
$animate.triggerReflow(); //reflow
|
||||
expect(element.children().length).toBe(5);
|
||||
|
||||
for(i = 1; i < 5; i++) {
|
||||
expect(kids[i]).not.toHaveClass('ng-enter-active');
|
||||
expect(kids[i]).toHaveClass('ng-enter-pending');
|
||||
}
|
||||
|
||||
expect($browser.deferredFns.length).toEqual(5); //4 staggers + 1 combined timeout
|
||||
|
||||
forEach(cancellations, function(promise) {
|
||||
$animate.cancel(promise);
|
||||
});
|
||||
|
||||
for(i = 1; i < 5; i++) {
|
||||
expect(kids[i]).not.toHaveClass('ng-enter');
|
||||
expect(kids[i]).not.toHaveClass('ng-enter-active');
|
||||
expect(kids[i]).not.toHaveClass('ng-enter-pending');
|
||||
}
|
||||
|
||||
//the staggers are gone, but the global timeout remains
|
||||
expect($browser.deferredFns.length).toEqual(1);
|
||||
}));
|
||||
|
||||
|
||||
it("should not allow the closing animation to close off a successive animation midway",
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
|
||||
@@ -1873,7 +1943,7 @@ describe("ngAnimate", function() {
|
||||
|
||||
|
||||
it("should apply staggering to both transitions and keyframe animations when used within the same animation",
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) {
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement, $browser) {
|
||||
|
||||
if(!$sniffer.transitions) return;
|
||||
|
||||
@@ -1903,14 +1973,23 @@ describe("ngAnimate", function() {
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.triggerReflow();
|
||||
expect($browser.deferredFns.length).toEqual(3); //2 staggers + 1 combined timeout
|
||||
|
||||
expect(elements[0].attr('style')).toBeFalsy();
|
||||
expect(elements[1].attr('style')).toMatch(/animation-play-state:\s*paused/);
|
||||
expect(elements[2].attr('style')).toMatch(/animation-play-state:\s*paused/);
|
||||
|
||||
expect(elements[1].attr('style')).toMatch(/transition-delay:\s+1.1\d*/);
|
||||
expect(elements[1].attr('style')).toMatch(/animation-delay: 1\.2\d*s,\s*2\.2\d*s/);
|
||||
for(i = 1; i < 3; i++) {
|
||||
expect(elements[i]).not.toHaveClass('ng-enter-active');
|
||||
expect(elements[i]).toHaveClass('ng-enter-pending');
|
||||
}
|
||||
|
||||
expect(elements[2].attr('style')).toMatch(/transition-delay:\s+1.2\d*/);
|
||||
expect(elements[2].attr('style')).toMatch(/animation-delay: 1\.4\d*s,\s*2\.4\d*s/);
|
||||
$timeout.flush(0.4 * 1000);
|
||||
|
||||
for(i = 1; i < 3; i++) {
|
||||
expect(elements[i]).toHaveClass('ng-enter-active');
|
||||
expect(elements[i]).not.toHaveClass('ng-enter-pending');
|
||||
}
|
||||
|
||||
for(i = 0; i < 3; i++) {
|
||||
browserTrigger(elements[i],'transitionend', { timeStamp: Date.now() + 22000, elapsedTime: 22000 });
|
||||
|
||||
Reference in New Issue
Block a user