简体   繁体   中英

Angular.js animations

I have a block with ng-repeat that is defined like this:

<div ng-show="isPageSelected(item.page)" class="block" ng-repeat="item in data">
  ...
</div>

Currently I can switch between those blocks, by clicking certain elements. It works via ng-show="isPageSelected(item.page)" as you might have guessed. It all works fine, but they are switching instantly and I want to add an animation, a simple fade in/fade out will do.

So, the block that is no longer selected should fade out and when it's gone a new block should fade in. When I'm using ngAnimate they fade in and fade out simultaneously. I need the first block to disappear completely and be hidden with display: none; and when it's done the next block should appear and fade in. It's a rather straightforward task when using jQuery, but how do I do that elegantly with Angular.js?

I have a strong suspicion that Angular.js isn't exactly a good choice for a site with complex animations.

EDIT: To simplify my question, all I need to do is

  1. On a button click start an animation;
  2. When an animation has been finished, change model;
  3. Run another animation.

Since I need to change the model after an animation, it's probably not possible to do it via pure CSS. The only way I know of triggering animations on specific elements in angular is to create a directive, pass a scope variable into the directive, create watcher for that variable in the directive and then change the variable from the controller:

<div animation="page"></div>

app.directive('animation', function(){
    return {
        scope: { page: '=animation' },
        link: function(scope, element){
            scope.$watch('page', function(newVal){
                ...
            });
        }
    };
});

I guess it would work, but it seems really bloated to create a directive just for that. Also, how would I change $scope.page with this approach only when the animation has been finished? Add another scope variable just to trigger an animation and somehow change $scope.page when an animation has been finished? It's possible to do it with ngFx module, but the amount of code it takes is just ridiculous. At this point I think adding jQuery animations to the controller would be a prettier way to solve it.

EDIT: That's how it looks like with jQuery animations:

$scope.changePage = function(page) {
  $('.block').animate({opacity: 0}, 500, function(){
    $scope.page.id = page;
    $scope.$apply();
    $(this).animate({opacity: 1}, 500);
  });
};

It works fine and it's not quite as verbose as the way with directives, but I have to use CSS selectors and that's just feels very "unangular". Do you guys use something similar when dealing with animations?

EDIT: Somewhat similar approach using ngFx :

    <div ng-hide="switchPageAnimation" 
             class="block fx-fade-normal fx-speed-300 fx-trigger">

In the controller:

  $scope.switchPageAnimation = false;

  $scope.changePage = function(page) {
    if($scope.page.id === page || $scope.switchPageAnimation) return;
    $scope.switchPageAnimation = true;
    $scope.$on('fade-normal:enter', function(){
      $scope.page.id = page;
      $scope.switchPageAnimation = false;
    });
  };

I'm not using CSS selectors, but still it looks awful. I have to define a scope variable for the animation and then check if the animation is already running. I feel like I am missing something really obvious.

Maybe this will help you and it is not necessary in your case to wait for the animation to finish. If your pages have css position: absolute and are in a container with position: relative then they share the same place and are not shown one below the other while animation. With this setting you can crossfade or delay the show animation

transition-delay:

.container1{
    position: relative;
    height:400px;
}

.block1{
    position:absolute;
}
.block1.ng-hide-add-active {
    display: block!important;
    -webkit-transition: 2s linear all;
    transition: 2s linear all;
}
.block1.ng-hide-remove-active {
    display: block!important;
    -webkit-transition: 2s linear all;
    transition: 2s linear all;
    -webkit-transition-delay: 2s;
    transition-delay: 2s;
}
.block1.ng-hide {
    opacity: 0;
}

http://jsfiddle.net/ncrs4gz0/

Edit: If u use a filter in ng-repeat instead of ng-show to show a selected page like this

<div  class="block1" ng-repeat="item in data | filter:isPageSelected">

then the pages are added and removed from the dom and angular add classes ng-enter , ng-enter-active and ng-leave ng-leave-active

but the animation can be defined similar see fiddle : http://jsfiddle.net/o944epzy/

Effectively you have to use CSS for showing and hiding elements from your ng-repeat; when you use ngAnimate, do not forget to inject it in your module.

.module('myModule', [  'ngAnimate', ...

Doing that when you will add/remove an element from you ng-repeat source of data. Angular will add and remove the class ng-enter, ng-leave . Here is a good tutorial about animation with ngAnimate.

In your own case you want to change page. I suggest you have too many items for displaying all at once.

You can declare in your scope two variable :

$scope.elemByPage = 5;
$scope.page = 0;
$scope.nbPages = ...; // I let you do the maths ;)

and after in your template you can do simply this:

<div class="my-repeat-item" data-ng-repeat="item in data | pager:page:elemByPage">
 {{item.xxx}}           
</div>

This template will only show the needed items in function of the page number and number of elements per page. Pager is a simple filter and it does the trick

.filter('pager',[function(){
        return function(items, page, nbElemByPage) {

            if(!nbElemByPage || nbElemByPage < 1) {
                return items;
            }

            var nbPages = Math.floor(items.length/nbElemByPage);
            if(nbPages<1) {
                return items;
            }

            var startIndex = page*nbElemByPage;

            return items.splice(startIndex, nbElemByPage);
        };
    }])

Now you just have to use button that will allow you to browser your items

<button data-ng-click="page = page - 1"/>Prev page</button>
<button data-ng-click="page = page - 1"/>Next page</button>

To finish you want to add a fade in animation on new items so declare in your css these classes following the class of your items (here my-repeat-item )

.my-repeat-item.ng-enter {
    transition: 0.6s ease all;
    opacity:0;
}
.my-repeat-item.ng-enter.ng-enter-active {
    opacity:1;
}

You can do the same thing when a item is removed by replacing enter with leave .

Hope it will answer to your question.

Effectively you have to use CSS for showing and hiding elements from your ng-repeat; when you use ngAnimate, do not forget to inject it in your module.

You can add your desired transition to the CSS class of the element to show/hide:

animate-show.ng-hide-add.ng-hide-add-active,
.animate-show.ng-hide-remove.ng-hide-remove-active {
  -webkit-transition: all linear 0.5s;
  transition: all linear 0.5s;
}

See more here: https://docs.angularjs.org/api/ng/directive/ngShow#example

In fact, using CSS is the 'right' way to do it even though it even though it might feel un-angular.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM