Reputation: 66
recently I really like Google I/O 2015 event page, especially those transition animations between different states. I know they used Polymer for that, but I'm trying to recreate such delayed animations in Angular (1.4.1) and Angular-material and ui-router.
Basically what I want to achieve is this workflow:
This is not trivial task, and ng-animate is not very helpful, but I want to use is as much as possible. One drawback is, that leaving css classes are not added, before promises are resolved, the other it, that at one moment, both - old and new state view are present on the page.
I tried to create this directive:
(function() {
'use strict';
angular
.module('climbguide')
.directive('cgAnimateElement', cgAnimateElement);
/* @ngInject */
function cgAnimateElement($animate, $rootScope, $state) {
return {
restrict: 'A',
link: linkFunc
};
function linkFunc(scope, el) {
$animate.enter(el, el.parent());
var cleanUp = $rootScope.$on('$stateChangeStart',
function(event, toState, toParams) {
if ($rootScope.stateChangeBypass) {
$rootScope.stateChangeBypass = false;
return;
}
event.preventDefault();
var promise = $animate.leave(el);
promise.then(function() {
$rootScope.stateChangeBypass = true;
$state.go(toState.name, toParams);
});
});
scope.$on('$destroy', function() {
cleanUp();
});
}
}
})();
It basically does what I want, however for some reason it is only possible to use it one element - I assume because of the $rootScope.$on('$stateChangeStart') and later use of $state.go(toState.name, toParams);.
I also found two other solutions,
angular-ui-router-in-out, which uses CSS, but there is waiting for promises to be resolved before any animation happens (the loader animation would be necessary)
angular-gsapify-router, which uses javascript animations, and has the same problem as above one.
I'm still only learning angular, so I really don't know how to do this in a right way. Do you have any ideas? Thanks a lot.
P.S.: sorry for missing links to the libraries, but this is my first post to SO, so I can only post 2 links :)
Upvotes: 2
Views: 407
Reputation: 66
Maybe it will help somebody, but I got it to work with probably dirty hack, but anyway it does, what I described in the original post.
I changed the directive so it can be re-used more times:
(function() {
'use strict';
angular
.module('climbguide')
.directive('cgAnimateElement', cgAnimateElement);
/* @ngInject */
function cgAnimateElement($animate, delayedRouterService) {
return {
restrict: 'A',
link: linkFunc
};
function linkFunc(scope, el) {
var stateChangeBypass = false;
$animate.enter(el, el.parent());
// Use scope instead of $rootScope, so there is no need to de-register listener
scope.$on('$stateChangeStart',
function(event, toState, toParams) {
if (stateChangeBypass) {
// Resuming transition to the next state broadcasts new $stateChangeStart
// event, so it necessary to bypass it
stateChangeBypass = false;
return;
}
delayedRouterService.holdStateChange(event);
var promise = $animate.leave(el);
promise.then(function() {
stateChangeBypass = true;
delayedRouterService.releaseStateChange(toState, toParams);
});
});
}
}
})();
I created service for handling state changes - preventing and resuming state changes in ui-router:
(function() {
'use strict';
angular
.module('climbguide')
.factory('delayedRouterService', delayedRouterService);
/* @ngInject */
function delayedRouterService($state) {
var _runningAnimations = 0;
/**
* Public methods
*
*/
var service = {
holdStateChange: holdStateChange,
releaseStateChange: releaseStateChange
};
return service;
//////////////
/**
* Prevent state change from the first animation
* Store the number of currently running animations
*
* @param event
*/
function holdStateChange(event) {
if (_runningAnimations === 0) {
event.preventDefault();
}
_runningAnimations++;
}
/**
* Remove animation from the stack after it is finished
* Resume state transition after last animation is finished
*
* @param toState
* @param toParams
*/
function releaseStateChange(toState, toParams) {
_runningAnimations--;
if (_runningAnimations === 0) {
$state.go(toState.name, toParams);
}
}
}
})();
So it's possible to use it in the HTML for the element which I want to animate
<div class="main" cg-animate-element>
...
</div>
And the final CSS:
.main {
&.ng-animate {
transition: opacity, transform;
transition-duration: 0.4s;
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1);
}
&.ng-enter {
opacity: 0;
transform: translate3d(0, 100px, 0);
}
&.ng-enter-active {
opacity: 1;
transform: translate3d(0, 0, 0);
}
&.ng-leave {
opacity: 1;
transform: translate3d(0, 0, 0);
}
&.ng-leave-active {
opacity: 0;
transform: translate3d(0, 100px, 0);
}
}
Upvotes: 1