angular / angular.js

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

ng-view dynamic CSS3 animations #8375

Closed xpepermint closed 7 years ago

xpepermint commented 10 years ago

Hi. I created a module that dynamically set the animation class for ng-view based on the DOM element attribute.

Here's a module:

/**
 * The following code adds support for `ng-new` dynamic transitions.
 */
angular.module('ngAnimateView', ['ngAnimate'])

// Implementing the `ng-view` dynamic animations. We extend the `$rootScope`
// thus every controller will have access to all new methods.
.run(function($rootScope, $location, $document) {

  // Setting the default `ng-view` animation class name.
  $rootScope.viewDefaultAnimationClass = '';
  // Setting the currently active `ng-view` animation class name to default
  // class name.
  $rootScope.viewAnimationClass = $rootScope.viewDefaultAnimationClass;

  // Sets the `ng-view` animation class name. If the parameter is `null` then
  // the default animation will be set.
  $rootScope.setViewAnimationClass = function(anim) {
    $rootScope.viewAnimationClass = anim || $rootScope.viewDefaultAnimationClass;
  };
  // Returns the current `ng-view` animation class name.
  $rootScope.getViewAnimationClass = function(anim) {
    return $rootScope.viewAnimationClass;
  };

  // Navigates to the requested URL with desired animation.
  $rootScope.to = function(url, anim) {
    $rootScope.setViewAnimationClass(anim);
    $location.path(url);
  };
})

// Extending the built-in `ng-href` directive with the support for transitions.
// If the `ng-view-animation` attribute exists on a DOM element that triggered
// the `ng-href` event then the `ng-view` will use this animation class for
// page transitions.
.directive('ngHref', function($rootScope, $location) {
  return {
    priority: 100, // make sure it runs before the built-in `ng-href` directive
    link: function(scope, element, attr) {
      element.bind('click', function() {
        $rootScope.setViewAnimationClass(attr.ngViewAnimation);
      })
    }
  }
});

and you would use it like this:

<ng-view class="{{viewAnimationClass}}" />
<a ng-href='/where_to_go' ng-view-animation="fade">Go back</a>

Btw, it would be very nice to have that kind of feature built-in. It would add a new level of UI experience to every angular app. Well ... I thought my module will fix my problem but nooooo :).

The module works but when a user clicks the back/forward button in a browser the class name does not change so the last animation class is always active. Here is an example app with the problem I described (I borrowed this example from @lockenj).

This thing is really annoying and I can't use animations if that can't be fixed because it destroys the whole concept of having dynamic animations on ng-view. Users often use these browser buttons. I hope a smart guy will tell me that this is something that could be fixed, that it is already built into angular, maybe that this is a bug or maybe there is a library that offers this out of the box. Thanks!

caitp commented 10 years ago

if you want the back/forward buttons to affect this, that's a bit more complicated because it means we need to associate animation state with browser history, which currently does not happen at all.

I think supporting html5 history this way would be doable, but considerably harder to implement a fake history stack to associate data with when html5mode is disabled.

It's kind of a cool idea, but it's complicated, and would require hacks on $location and ngAnimate to support this. Maybe we could talk about it today and see if we can bikeshed something for this.

caitp commented 10 years ago

this might actually work automatically in html5Mode, I'm not sure

xpepermint commented 10 years ago

Thanks @caitp. I use

.config(function($routeProvider, $locationProvider) {
  $locationProvider.html5Mode(true);
  ...
});

but it does not work :(.

hybrisCole commented 10 years ago

Hey, I have this slide animation between views using Velocity.js, it could work for you https://github.com/hybrisCole/ng-slide-velocity

xpepermint commented 10 years ago

@hybrisCole I see that you are animating with javascript using the animation function. Yeah that would solve some problems. I believe that the browser back/forward button will not use the proper animation. I assume the browser is not aware what animation should be used or better what animation was used for presenting the view at some point in the browser's history. Am I right?

nodkrot commented 10 years ago

That is as close as I can get:

App.directive('backAnimation', ['$browser', '$location',
    function($browser, $location) {
        return {
            link: function(scope, element) {
                $browser.onUrlChange(function(newUrl) {
                    if ($location.absUrl() === newUrl)
                        element.addClass('reverse');
                });
                scope.__childrenCount = 0;
                scope.$watch(function() {
                    scope.__childrenCount = element.children().length;
                });
                scope.$watch('__childrenCount', function(newCount, oldCount) {
                    if (newCount !== oldCount && newCount === 1)
                        element.removeClass('reverse');
                });
            }
        };
    }
]);
<div back-animation>
    <div class="page {{pageClass}}" ng-class="{slide: mainVisitedOnce}" ng-view></div>
</div>

Any new solutions? May be in 1.3?

gkalpak commented 7 years ago

What you are trying to do here, is associate each history entry with some app state (the viewAnimationClass). You might be able to make it work in HTML5 mode, by storing the value of viewAnimationClass along with each state, then listen for the popstate event and restore the current viewAnimationClass to the appropriate value. But I can't think how this would work in hashbang mode (except with a custom service that somehow tracks the forward/backward transitions).

In any case, I am going to close this, since:

  1. I don't think this a common enough usecase to make it worth adding to the core.
  2. It didn't gain much traction.
  3. It should be possible to implement something like this outside of core.