angular / angular.js

AngularJS - HTML enhanced for web apps!
https://angularjs.org
MIT License
58.82k stars 27.5k forks source link

transition events not always fired #14120

Closed wesleycho closed 8 years ago

wesleycho commented 8 years ago

Note: for support questions, please use one of these channels: https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#question. This repository's issues are reserved for feature requests and bug reports.

Bug

In certain situations, when the user switches to a different tab, the transitionend event is not fired - this is a documented browser defect with Firefox here, and seems to be reproducible in Chrome at the least.

Not minimal unfortunately, but here is an example from UI Bootstrap. Source code for component can be found here - in particular, the culprit is that this listener never fires due to this browser defect, which causes the callback to never execute.

The transitionend event fires and the slides continue rotating since the $currentTransition internal state is reset.

My proposal is to have ngAnimate use the computed style information to manually fire the transitionend event if it is never fired to cause the listeners to be resolved - a timeout for the transition time period + 1 ms would be satisfactory to me, which would prevent library maintainers to have to do an extra getComputedStyle call & an extra calculation of the transition time length. This is a hack, but this is the least onerous solution IMO.

Angular 1.4+ when using $animateCss instead of $animate. This affects at the least Chrome & Firefox across all OSs at the least.

Narretz commented 8 years ago

animateCss actually already has a timeout that should end the animation after (I think) duration * 1.5. That the end listener isn't executed seems like a bug, though. Or maybe that's because animateCss should be used mainly in the context of js animations, not as a replacement for general animations.

Narretz commented 8 years ago

I think this is the problem: https://github.com/angular/angular.js/issues/12691 $animateCss wasn't designed as an all out replacement for $animate. Hmm ... Why did bootstrap-ui change to using animateCss?

wesleycho commented 8 years ago

I thought $animateCss was recommended for when the transitions were purely from CSS classes? Was I mistaken there?

I've no problem changing it to $animate here if that's what does the trick.

wesleycho commented 8 years ago

I took a look at changing it to $animate, I am seeing this same problem still - I believe the $animate.on('addClass', ...) listener is not getting called on completion of the custom JS animation here. Is that a possibility?

Narretz commented 8 years ago

Ok, that's weird. I have a look tomorrow

jgrund commented 8 years ago

We are seeing this same issue. It appears to be related to AnimationRunner._tick switching to timeouts when in a background tab.

It is completely non-deterministic, but when it occurs you won't see another update until the app switches back to using $$raf to schedule updates.

johnsonw commented 8 years ago

I am running into an issue with angular 1.5.0 and 1.4.9 where animations are delayed when switching to a different tab and then coming back. It's as if the digest cycle doesn't take place for a few seconds after switching back to the tab. I tried reverting 9a60408c804a62a9517857bdb9a42182ab6769e3 locally and the problem was fixed. As @jgrund mentioned above, when the problem occurs, the update doesn't take place until the $$raf update takes place.

Narretz commented 8 years ago

So we actually switched from using $$rAF when the page is hidden to $timeout because most browsers don't flush requestAnimationFrame when the page is hidden. A simple demo obviously helps with fixing this.

Narretz commented 8 years ago

So the problem is that we don't notify progress when the animation is skipped. (it is skipped because the document is hidden). "Skipping" means that no animation is performed - classes and styles etc. are applied as you would expect. Progress events are for listeners registered with $animate.on(event, element, callbackFn), where event is for example 'addClass' or ' enter'

So we have a few scenarios for which we need to define the behavior:

What happens to an animation that is started while the document is hidden? (e.g the case of the slideshow, when a transition is started while the document is hidden)

  1. the animation result is executed, but no animation is played (currently intended behavior on master, but broken because the progress listeners are not called)
  2. the animation stops completely, and picks up where it left off when the document becomes visible again

I think option 1 is what makes most sense. Option 2 would actually be very diffcult to implement. Fwiw, I think the behavior before the timeout tick "fix" was that of option 2. The animation listeners would only be resolved when the document became visible again.

Since there are more conditions under which an animation is skipped, I have to check in which cases it makes sense to fire the animation callbacks, and in which it doesn't. My hunch is that you will want the listeners almost always to fire, even if no actual animation is performed.

Should animation progress listeners be fired when ngAnimate is not included? At the moment, we don't notify progress without ngAnimate at all, even though the docs suggest we do. If this worked, then 3rd party module developers wouldn't have to have two paths for animations enabled / not enabled. We also execute the then and done functions of the runner without ngAnimate. I think we changed this pretty recently, so I assume the intention is to actually fire all listeners always.

Should animateCss notify progress listeners if used programmatically? Atm, animateCss doesn't notify progress at all at the moment if it is used programmatically. It does notify when it is used by the general animation service ($animate) and when it is used inside javascript animations (which are also called from $animate).

@matsko I would like your opinion on this

jgrund commented 8 years ago

Just FYI we were also able to reproduce this when not loading the ngAnimate module.

Narretz commented 8 years ago

@jgrund Do you mean the $animate.on() listeners are not fired when ngAnimate is not included and the document is hidden? At the moment, they never fire without ngAnimate. This is one of the points I raised in https://github.com/angular/angular.js/issues/14120#issuecomment-188755085

jgrund commented 8 years ago

@Narretz I mean, whenever the runner switches to timeouts in a background tab, the state of our app does not update upon refocusing. This occurs whether ngAnimate has been loaded or not.

Narretz commented 8 years ago

@jgrund Can you please provide a demo of that problem? Because I cannot reproduce it in this app with ngAnimate removed: http://plnkr.co/edit/pGRtrLoA7NOWfeOAQYnW?p=preview