mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-04-12 02:50:58 +00:00
fix($animate): cancel any ongoing child animations during move and leave animations
This commit is contained in:
parent
079dd93991
commit
3f31a7c769
2 changed files with 110 additions and 12 deletions
|
|
@ -200,6 +200,7 @@ angular.module('ngAnimate', ['ng'])
|
|||
var selectors = $animateProvider.$$selectors;
|
||||
|
||||
var NG_ANIMATE_STATE = '$$ngAnimateState';
|
||||
var NG_ANIMATE_CLASS_NAME = 'ng-animate';
|
||||
var rootAnimateState = {running:true};
|
||||
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope',
|
||||
function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope) {
|
||||
|
|
@ -320,6 +321,7 @@ angular.module('ngAnimate', ['ng'])
|
|||
* @param {function()=} done callback function that will be called once the animation is complete
|
||||
*/
|
||||
leave : function(element, done) {
|
||||
cancelChildAnimations(element);
|
||||
$rootScope.$$postDigest(function() {
|
||||
performAnimation('leave', 'ng-leave', element, null, null, function() {
|
||||
$delegate.leave(element, done);
|
||||
|
|
@ -358,6 +360,7 @@ angular.module('ngAnimate', ['ng'])
|
|||
* @param {function()=} done callback function that will be called once the animation is complete
|
||||
*/
|
||||
move : function(element, parent, after, done) {
|
||||
cancelChildAnimations(element);
|
||||
$delegate.move(element, parent, after);
|
||||
$rootScope.$$postDigest(function() {
|
||||
performAnimation('move', 'ng-move', element, null, null, function() {
|
||||
|
|
@ -503,6 +506,10 @@ angular.module('ngAnimate', ['ng'])
|
|||
done:done
|
||||
});
|
||||
|
||||
//the ng-animate class does nothing, but it's here to allow for
|
||||
//parent animations to find and cancel child animations when needed
|
||||
element.addClass(NG_ANIMATE_CLASS_NAME);
|
||||
|
||||
forEach(animations, function(animation, index) {
|
||||
var fn = function() {
|
||||
progress(index);
|
||||
|
|
@ -519,12 +526,6 @@ angular.module('ngAnimate', ['ng'])
|
|||
}
|
||||
});
|
||||
|
||||
function cancelAnimations(animations) {
|
||||
var isCancelledFlag = true;
|
||||
forEach(animations, function(animation) {
|
||||
(animation.endFn || noop)(isCancelledFlag);
|
||||
});
|
||||
}
|
||||
|
||||
function progress(index) {
|
||||
animations[index].done = true;
|
||||
|
|
@ -538,11 +539,34 @@ angular.module('ngAnimate', ['ng'])
|
|||
function done() {
|
||||
if(!done.hasBeenRun) {
|
||||
done.hasBeenRun = true;
|
||||
element.removeData(NG_ANIMATE_STATE);
|
||||
cleanup(element);
|
||||
(onComplete || noop)();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cancelChildAnimations(element) {
|
||||
angular.forEach(element[0].querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
|
||||
element = angular.element(element);
|
||||
var data = element.data(NG_ANIMATE_STATE);
|
||||
if(data) {
|
||||
cancelAnimations(data.animations);
|
||||
cleanup(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cancelAnimations(animations) {
|
||||
var isCancelledFlag = true;
|
||||
forEach(animations, function(animation) {
|
||||
(animation.endFn || noop)(isCancelledFlag);
|
||||
});
|
||||
}
|
||||
|
||||
function cleanup(element) {
|
||||
element.removeClass(NG_ANIMATE_CLASS_NAME);
|
||||
element.removeData(NG_ANIMATE_STATE);
|
||||
}
|
||||
}]);
|
||||
|
||||
$animateProvider.register('', ['$window', '$sniffer', function($window, $sniffer) {
|
||||
|
|
|
|||
|
|
@ -696,8 +696,9 @@ describe("ngAnimate", function() {
|
|||
$rootScope.$digest();
|
||||
|
||||
if ($sniffer.transitions) {
|
||||
expect(element.hasClass('abc ng-enter')).toBe(true);
|
||||
expect(element.hasClass('abc ng-enter ng-enter-active')).toBe(true);
|
||||
expect(element.hasClass('abc')).toBe(true);
|
||||
expect(element.hasClass('ng-enter')).toBe(true);
|
||||
expect(element.hasClass('ng-enter-active')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 22000, elapsedTime: 22000 });
|
||||
}
|
||||
expect(element.hasClass('abc')).toBe(true);
|
||||
|
|
@ -708,7 +709,8 @@ describe("ngAnimate", function() {
|
|||
|
||||
if ($sniffer.transitions) {
|
||||
expect(element.hasClass('xyz')).toBe(true);
|
||||
expect(element.hasClass('xyz ng-enter ng-enter-active')).toBe(true);
|
||||
expect(element.hasClass('ng-enter')).toBe(true);
|
||||
expect(element.hasClass('ng-enter-active')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11000 });
|
||||
}
|
||||
expect(element.hasClass('xyz')).toBe(true);
|
||||
|
|
@ -732,8 +734,10 @@ describe("ngAnimate", function() {
|
|||
$animate.enter(element, parent);
|
||||
$rootScope.$digest();
|
||||
if($sniffer.transitions) {
|
||||
expect(element.hasClass('one two ng-enter')).toBe(true);
|
||||
expect(element.hasClass('one two ng-enter ng-enter-active')).toBe(true);
|
||||
expect(element.hasClass('one')).toBe(true);
|
||||
expect(element.hasClass('two')).toBe(true);
|
||||
expect(element.hasClass('ng-enter')).toBe(true);
|
||||
expect(element.hasClass('ng-enter-active')).toBe(true);
|
||||
expect(element.hasClass('one-active')).toBe(false);
|
||||
expect(element.hasClass('two-active')).toBe(false);
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3000 });
|
||||
|
|
@ -1574,4 +1578,74 @@ describe("ngAnimate", function() {
|
|||
expect(element.contents().length).toBe(1);
|
||||
}));
|
||||
|
||||
it("should cancel all child animations when a leave or move animation is triggered on a parent element", function() {
|
||||
|
||||
var animationState;
|
||||
module(function($animateProvider) {
|
||||
$animateProvider.register('.animan', function($timeout) {
|
||||
return {
|
||||
enter : function(element, done) {
|
||||
animationState = 'enter';
|
||||
$timeout(done, 0, false);
|
||||
return function() {
|
||||
animationState = 'enter-cancel';
|
||||
}
|
||||
},
|
||||
addClass : function(element, className, done) {
|
||||
animationState = 'addClass';
|
||||
$timeout(done, 0, false);
|
||||
return function() {
|
||||
animationState = 'addClass-cancel';
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($animate, $compile, $rootScope, $timeout, $sniffer) {
|
||||
var element = html($compile('<div class="parent"></div>')($rootScope));
|
||||
var container = html($compile('<div class="container"></div>')($rootScope));
|
||||
var child = html($compile('<div class="animan child"></div>')($rootScope));
|
||||
|
||||
ss.addRule('.animan.ng-enter, .animan.something-add', '-webkit-transition: width 1s, background 1s 1s;' +
|
||||
'transition: width 1s, background 1s 1s;');
|
||||
|
||||
$rootElement.append(element);
|
||||
jqLite(document.body).append($rootElement);
|
||||
|
||||
$animate.enter(child, element);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(animationState).toBe('enter');
|
||||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-enter')).toBe(true);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(true);
|
||||
}
|
||||
|
||||
$animate.move(element, container);
|
||||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-enter')).toBe(false);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(false);
|
||||
}
|
||||
|
||||
expect(animationState).toBe('enter-cancel');
|
||||
$rootScope.$digest();
|
||||
$timeout.flush();
|
||||
|
||||
$animate.addClass(child, 'something');
|
||||
expect(animationState).toBe('addClass');
|
||||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('something-add')).toBe(true);
|
||||
expect(child.hasClass('something-add-active')).toBe(true);
|
||||
}
|
||||
|
||||
$animate.leave(container);
|
||||
expect(animationState).toBe('addClass-cancel');
|
||||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('something-add')).toBe(false);
|
||||
expect(child.hasClass('something-add-active')).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue