I have the following case: I'm using the ui-router for the routing in my AngularJS application. In one route, there are five child states for different subscreens. I want to animate the transition between these in a carousel-like manner.
The navigation looks like this:
Link to A | Link to B | Link to C | Link to D | Link to E
Navigating from state A
to state B
should make screen A
slide out to the left and screen B
slide in from the right; vice versa for navigating from state B
to state A
.
What does work is animating the screen transitions with transform: translateX(...);
on enter
and leave
in one direction only.
Usually, I control my animations using ng-class
with a flag. In this case, however, setting a class on the ui-view
element doesn't work at all (Angular 1.2 and ui-router 0.2 aren't completely compatible yet). Neither is it working with setting it with a custom directive listening to scope.$on "$stateChangeStart"
which is fired after the transition has begun.
How can I implement the desired behavior?
Edit: The solution
For the record: I ended up implementing it using a custom $scope
function using $state.go()
to determine the direction before changing the route. This avoids the $digest already in progress
errors. The class determining the animation is added to the ui-view
's parent element; this animates both the current as well as the future ui-view
in the correct direction.
Controller function (Coffeescript):
go: (entry) ->
fromIdx = ...
toIdx = ...
if fromIdx > toIdx
$scope.back = false
else
$scope.back = true
$state.go entry
Template:
<div ng-class="{toLeft: back}">
<div ui-view></div>
</div>
Angular's animation system is built on CSS functionality, which means you can animate any property that the browser considers animatable. This includes positions, sizes, transforms, colors, borders, and more. The W3C maintains a list of animatable properties on its CSS Transitions page.
PageRouteBuilder<T> class Null safety. A utility class for defining one-off page routes in terms of callbacks. Callers must define the pageBuilder function which creates the route's primary contents.
PageRouteBuilder provides an Animation object. This Animation can be used with Tween and Curve objects to customize the transition animation. You need to define a pageBuilder function to create the route's content and define the transitionBuilder function to add transition animation.
You can control the classes on your view by setting up a controller to do that specifically. You can then subscribe to events within the app and change the way the page animates.
<div class="viewWrap" ng-controller="viewCtrl">
<div class="container" ui-view ng-class="{back: back}"></div>
</div>
Then within your controller
.controller('viewCtrl', function ($scope) {
$scope.$on('$stateChangeSuccess', function (event, toState) {
if (toState.name === 'state1') {
$scope.back = true;
} else {
$scope.back = false;
}
});
});
I've set up a codepen to demonstrate here http://codepen.io/ed_conolly/pen/aubKf
For anybody trying to do this please note that I've had to use the ui.router.compat module due to the current incompatibility of the animations in Angular 1.2 and UI Router.
You could always checkout the angular-1.2 branch: https://github.com/angular-ui/ui-router/tree/angular-1.2. This fixes up ngAnimate along with a few other things.
I believe this will be included in ui-router 0.3.0, so as long as you won't be pushing live soon, this should provide you with the function you need until you can get back on the 'stable' branch.
Disclaimer: I have no authority on when the next release of UI-router will be or what it will include. I have simply found this information in various issues on the github page.
Eddiec's solution was a really nice start but I still found myself hacking it up a bit. Here is a modified controller that works better for my purposes. This is more dynamic:
function ViewCtrl($scope, $state) {
$scope.$on('$stateChangeStart', function (event, toState) {
var movingToParent = $state.includes(toState.name);
if (movingToParent) {
$scope.back = true;
} else {
$scope.back = false;
}
});
}
I was attempting to do this same thing with ngRoute
using $routeChangeSuccess
but the problem I ran into was that the ng-leave
animation would start before a digest cycle ran. The enter animation was always correct, but the leave animation was always the previous one.
The solution that worked for me (Angular 1.57) was to wrap the $location.path()
call in a $timeout
. This makes sure a full digest runs before the animation is started.
function goto(path) {
if(nextIndex > currentIndex) {
ctrl.transition = 'slide-out-left';
} else {
ctrl.transition = 'slide-out-right';
}
$timeout(function() {
$location.path(path);
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With