Best practice for animating multiple ui-views as one
Asked Answered
W

1

8

I have an app that basically looks like this

<div ui-view="header">
<div ui-view="main">
<div ui-view="footer">

Now, the footer will stay the same for all different states of the app, but the header will change in some states, but also share content in a lot of the states. The only ui-view that will change across all states is ui-view="main".

Currently my $stateProvider looks like this (footer not implemented yet):

app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
  $stateProvider.state('root',{
      url: '',
      abstract: true,
      views: {
        'header': {
          templateUrl: appHelper.views+'/header.html',
        },
      }
    })
    .state('root.home', {
      url: '/',
      views: {
        'header': {
          templateUrl: appHelper.views+'/header.html',
        },
        'main@': {
          templateUrl: appHelper.views+'/home.html',
          controller: 'HomeController as homeVm',
        }
      },
      resolve: {
        posts: function(dataService) {
          return dataService.getPosts();
        },
        artists: function(dataService) {
          return dataService.getArtists();
        }
      }
    })
    .state('root.artist', {
      url: '/artist/:slug',
      views: {
        'header@': {
          templateUrl: appHelper.views+'/artistHeader.html',
          controller: 'ArtistController as artistVm',
        },
        'main@': {
          templateUrl: appHelper.views+'/artist.html',
          controller: 'ArtistController as artistVm',
        }
      },
      resolve: {
        artist: function($stateParams, dataService) {
          return dataService.getArtist($stateParams.slug);    
        }
      }
    });
}]);

So far, so good. But here’s the catch. During transitions I want to animate all the ui-views as one. To make things look cohesive in my case (it’s a fade animation) I would like to put them in a wrapping div and have that div do the fade out/fade in (doing it on each different ui-view will cause all kinds of ugliness).

What is best practice in this scenario?

Do I wrap them in a ui-view and hook that up in the $stateProvider somehow (nested ui-views?). Are there any other ways to do it? Can I listen to $stateChange and apply classes to my wrapper like ngAnimate would with an ui-view? (fade out then wait until entirely faded out with a successful resolve before fading in).

From my googling efforts it seems ui-view is much preferred when handling animations of the transition type.

Worldshaking answered 24/6, 2015 at 17:16 Comment(2)
Animations in the $stateChangeStart and $stateChangeSuccess events have some odd side effects, so avoid them (at the moment). Until the ui-router has a better way to manage the transition promise other than event.preventDefault() animations through JS will be tricky. Running three ui-views and animating them all syncronously should not be a problem through pure CSS. Take a look in the ui-routers FAQ section, there is a section dedicated to animating transitions.Fey
While the animation indeed is syncronously it causes problem with the design as the header is fixed to the top of the viewport during scroll. When the fade out starts it'll reveal the content of main underneath which is not desired. With a wrapper this is obviously not a problem as it'll fade as one single element..Worldshaking
W
3

... During transitions I want to animate all the ui-views as one. To make things look cohesive in my case (it’s a fade animation) I would like to put them in a wrapping div and have that div do the fade out/fade in (doing it on each different ui-view will cause all kinds of uglyness).

What is best practice in this scenario?

Option 1: Using event hooks $stateChangeStart and $stateChangeSuccess

This approach would have some side effects as @David mentioned in comments. So avoid it until ui-router comes up with a better way to manage state transition promise. Currently it relies on event.preventDefault() to prevent the transition from happening.

Basically, ui-router will fire your ui-view route change view-out at the same time as the incoming view-in. So what you get is the previous ui-view still visible while the next ui-view is rendered and if you're animating the view-out then there will be that transition duration of overlap between the old view and the new. You should look into holding off on rendering the new ui-view until the old one is finished animating out (before animating the new one in)

For similar reasons, wrapping them in another ui-view and hooking them up in the $stateProvider would not be wise.

Option 2:(Recommended) CSS based animations

I mostly go with CSS based transitions and you may achieve this either by

  • assigning a common CSS class to each ui-view and applying animation on that class.
  • or enclosing the three ui-view in a <div>(if you really need this) and apply animation on it.

Either way, you would be treating all ui-views as a single entity and animating them would make things look more cohesive.

HTML:

<div ui-view="header" ></div>    
<div ui-view="main" ></div>
<div ui-view="footer" ></div>

CSS:

[ui-view].ng-enter { 
   animation: fadein 2s;
}

@keyframes fadein {
    from { opacity: 0; }
    to   { opacity: 1; }
}

Here's a working plunkr based on your use case where, I have an abstract state root and three nested states root.home, root.artist and root.band. While the ui-view='main' gets changed across all states, ui-view='footer' doesnt change in any and ui-view='header' changes for the state 'root.artist'. The fact that you are using css based .ng-enter and .ng-leave classes to trigger the animations, you technically overcome the issues with previous approach.

  $stateProvider
        .state('root',{
          abstract:true,
            views:{
              'header':{  //absolutely targets the 'header' view in root unnamed state.
                templateUrl:'header.html'
              },
              'footer':{  //absolutely targets the 'footer' view in root unnamed state.
                templateUrl:'footer.html'
              }
            }
        })
        .state('root.home',{
            views:{
              'main@':{
                templateUrl:'main.html'
              },
            }          
        })
        .state('root.artist',{
            views:{
              'header@':{
                templateUrl:'artist-header.html'
              },
              'main@':{
                templateUrl:'artist-main.html'
              },
            }          
        })
        .state('root.band',{
            views:{
              'main@':{
                templateUrl:'band-main.html'
              },
            }          
        })
Walz answered 5/7, 2015 at 20:34 Comment(1)
Hey @NLN, thanks for your extensive reply! I've updated your plunkr a bit to better reflect the issue I'm facing: plnkr.co/edit/mLUqKIjby9WGZ60k56ym?p=preview With the fade out you also have to absolute position the divs which makes it a bit difficult with the footer. Thus I want to be able to animate the parent div rather than the individual divs (as you can see the red and blue divs overlaps which doesn't look good at all when the opacity kicks in).Worldshaking

© 2022 - 2024 — McMap. All rights reserved.