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

Commit 4acc28a

Browse files
matskomhevery
authored andcommitted
feat(ngAnimate): cancel previous incomplete animations when new animations take place
1 parent c8197b4 commit 4acc28a

File tree

2 files changed

+121
-8
lines changed

2 files changed

+121
-8
lines changed

src/ng/animator.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ var $AnimatorProvider = function() {
264264
var animationPolyfill = $animation(className);
265265
var polyfillSetup = animationPolyfill && animationPolyfill.setup;
266266
var polyfillStart = animationPolyfill && animationPolyfill.start;
267+
var polyfillCancel = animationPolyfill && animationPolyfill.cancel;
267268

268269
if (!className) {
269270
beforeFn(element, parent, after);
@@ -281,7 +282,13 @@ var $AnimatorProvider = function() {
281282
return;
282283
}
283284

284-
element.data(NG_ANIMATE_CONTROLLER, {running:true});
285+
var animationData = element.data(NG_ANIMATE_CONTROLLER) || {};
286+
if(animationData.running) {
287+
(polyfillCancel || noop)(element);
288+
animationData.done();
289+
}
290+
291+
element.data(NG_ANIMATE_CONTROLLER, {running:true, done:done});
285292
element.addClass(className);
286293
beforeFn(element, parent, after);
287294
if (element.length == 0) return done();
@@ -354,10 +361,13 @@ var $AnimatorProvider = function() {
354361
}
355362

356363
function done() {
357-
afterFn(element, parent, after);
358-
element.removeClass(className);
359-
element.removeClass(activeClassName);
360-
element.removeData(NG_ANIMATE_CONTROLLER);
364+
if(!done.run) {
365+
done.run = true;
366+
afterFn(element, parent, after);
367+
element.removeClass(className);
368+
element.removeClass(activeClassName);
369+
element.removeData(NG_ANIMATE_CONTROLLER);
370+
}
361371
}
362372
};
363373
}

test/ng/animatorSpec.js

+106-3
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ describe("$animator", function() {
128128
}
129129
}
130130
});
131+
$animationProvider.register('custom-delay', function() {
132+
return {
133+
start: function(element, done) {
134+
window.setTimeout(done, 2000);
135+
},
136+
cancel : function(element) {
137+
element.addClass('animation-cancelled');
138+
}
139+
}
140+
});
131141
$animationProvider.register('setup-memo', function() {
132142
return {
133143
setup: function(element) {
@@ -140,14 +150,16 @@ describe("$animator", function() {
140150
}
141151
});
142152
})
143-
inject(function($animator, $compile, $rootScope) {
153+
inject(function($animator, $compile, $rootScope, $rootElement) {
144154
element = $compile('<div></div>')($rootScope);
145155
child = $compile('<div></div>')($rootScope);
146156
after = $compile('<div></div>')($rootScope);
157+
$rootElement.append(element);
147158
});
148159
})
149160

150161
it("should animate the enter animation event", inject(function($animator, $rootScope) {
162+
$animator.enabled(true);
151163
animator = $animator($rootScope, {
152164
ngAnimate : '{enter: \'custom\'}'
153165
});
@@ -158,6 +170,7 @@ describe("$animator", function() {
158170
}));
159171

160172
it("should animate the leave animation event", inject(function($animator, $rootScope) {
173+
$animator.enabled(true);
161174
animator = $animator($rootScope, {
162175
ngAnimate : '{leave: \'custom\'}'
163176
});
@@ -273,8 +286,6 @@ describe("$animator", function() {
273286
}));
274287

275288
it("should not run if animations are disabled", inject(function($animator, $rootScope) {
276-
$animator.enabled(true);
277-
$rootScope.$digest(); // clear initial animation suppression
278289
$animator.enabled(false);
279290

280291
animator = $animator($rootScope, {
@@ -293,6 +304,54 @@ describe("$animator", function() {
293304
window.setTimeout.expect(1).process();
294305
expect(element.text()).toBe('memento');
295306
}));
307+
308+
it("should only call done() once and right away if another animation takes place in between",
309+
inject(function($animator, $rootScope) {
310+
$animator.enabled(true);
311+
312+
animator = $animator($rootScope, {
313+
ngAnimate : '{hide: \'custom-delay\', leave: \'custom-delay\'}'
314+
});
315+
316+
element.append(child);
317+
318+
child.css('display','block');
319+
animator.hide(child);
320+
window.setTimeout.expect(1).process();
321+
expect(child.css('display')).toBe('block');
322+
323+
animator.leave(child);
324+
expect(child.css('display')).toBe('none'); //hides instantly
325+
326+
//lets change this to prove that done doesn't fire anymore for the previous hide() operation
327+
child.css('display','block');
328+
329+
window.setTimeout.expect(2000).process();
330+
expect(child.css('display')).toBe('block'); //doesn't run the done() method to hide it
331+
332+
expect(element.children().length).toBe(1); //still animating
333+
334+
window.setTimeout.expect(1).process();
335+
window.setTimeout.expect(2000).process();
336+
expect(element.children().length).toBe(0);
337+
}));
338+
339+
it("should call the cancel callback when another animation is called on the same element",
340+
inject(function($animator, $rootScope) {
341+
$animator.enabled(true);
342+
343+
animator = $animator($rootScope, {
344+
ngAnimate : '{hide: \'custom-delay\', show: \'custom-delay\'}'
345+
});
346+
347+
child.css('display','none');
348+
animator.show(element);
349+
window.setTimeout.expect(1).process();
350+
animator.hide(element);
351+
352+
expect(element.hasClass('animation-cancelled')).toBe(true);
353+
}));
354+
296355
});
297356

298357
describe("with CSS3", function() {
@@ -413,6 +472,28 @@ describe("$animator", function() {
413472
animator.show(element);
414473
expect(element[0].style.display).toBe('');
415474
}));
475+
476+
it("should finish the previous animation when a new animation is started",
477+
inject(function($animator, $rootScope, $compile, $sniffer) {
478+
if(!$sniffer.animations) return;
479+
480+
var style = 'animation: some_animation 2s linear 0s 1 alternate;' +
481+
vendorPrefix + 'animation: some_animation 2s linear 0s 1 alternate;'
482+
483+
element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
484+
var animator = $animator($rootScope, {
485+
ngAnimate : '{show: \'show\', hide: \'hide\'}'
486+
});
487+
488+
animator.show(element);
489+
window.setTimeout.expect(1).process();
490+
expect(element.hasClass('show')).toBe(true);
491+
expect(element.hasClass('show-active')).toBe(true);
492+
493+
animator.hide(element);
494+
expect(element.hasClass('show')).toBe(false);
495+
expect(element.hasClass('show-active')).toBe(false);
496+
}));
416497
});
417498

418499
describe("Transitions", function() {
@@ -488,6 +569,28 @@ describe("$animator", function() {
488569
}
489570
expect(element[0].style.display).toBe('');
490571
}));
572+
573+
it("should finish the previous transition when a new animation is started",
574+
inject(function($animator, $rootScope, $compile, $sniffer) {
575+
if(!$sniffer.animations) return;
576+
577+
var style = 'transition: 1s linear all;' +
578+
vendorPrefix + 'animation: 1s linear all;'
579+
580+
element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
581+
var animator = $animator($rootScope, {
582+
ngAnimate : '{show: \'show\', hide: \'hide\'}'
583+
});
584+
585+
animator.show(element);
586+
window.setTimeout.expect(1).process();
587+
expect(element.hasClass('show')).toBe(true);
588+
expect(element.hasClass('show-active')).toBe(true);
589+
590+
animator.hide(element);
591+
expect(element.hasClass('show')).toBe(false);
592+
expect(element.hasClass('show-active')).toBe(false);
593+
}));
491594
});
492595
});
493596

0 commit comments

Comments
 (0)