在ng-hide CSS转换完成之前,不要显示元素?

时间:2015-10-25 23:21:44

标签: javascript angularjs animation

简单的问题,但我遇到了实施问题。如果我有以下DOM设置:

<h1 class="fade" ng-repeat="child in parent.children" ng-show="parent.activeChild== child ">@{{ child.title }}</h1>

activeChild模型的parent属性发生变化时,如何淡出当前活动的子项,模型更改之前,然后淡入新的活动状态孩子更改后

我大致工作,只使用CSS过渡:

.fade.ng-hide-add {
    transition:opacity 1s ease;
}

.fade.ng-hide-remove {
    transition:opacity 1s ease 1s;
}

.fade.ng-hide-add {
    opacity:1;

    &.ng-hide-add-active {
        opacity:0;
    }
}

.fade.ng-hide-remove {
    opacity:0;

    &.ng-hide-remove-active {
        opacity:1;
    }
}

但是,这最终会产生this problem (Plunkr)

Gif

基本上,我想链接我的动画。我已经尝试过阅读ng-animate docs了,但我遇到了提供我想要的效果所需的语法问题。

我已经看到Angular文档有这样的内容:

app.animation('.fade', [function() {
    return {
        addClass: function(element, className, doneFn) {
        },
        removeClass: function(element, className, doneFn) {
        }
    };
}]);
  • 什么是className?这是我想要在淡入/淡出时应用的课程吗?我想要的课程是什么?
  • doneFn是什么意思?我认为它是动画完成后运行的功能吗?那里有什么?
  • 如果我已经拥有addClass,我会在removeClassdoneFn函数中执行什么操作?

目标

我想直接使用Angular的ngAnimate模块生成一个工作动画,使用CSS或JS。我怎样才能做到这一点?

7 个答案:

答案 0 :(得分:11)

为什么每个标题都使用单独的<h1>。您可以使用单个<h1>标记来显示标题。

我已经为您的问题创建了一个演示,我已成功完成您的要求。

<强>更新

注意,编辑代码以使用ngAnimate模块。当您使用ngAnimate模块时,它会在您隐藏元素时创建一个类.ng-hide

以下是您应用的控制器

app2.controller("testController", ["$scope", "$timeout", function ($scope, $timeout) {

    $scope.heading = {};
    $scope.heading.show = true;

    $scope.parent = {};
    $scope.parent.children = ["A", "B", "C", "D"];
    $scope.parent.activeChild = "A";

    $scope.changeHeading = function (child) {
        $timeout(function () {
            $scope.parent.activeChild = child;
            $scope.heading.show = true;
        }, 1000);

    }
}]);

你的html页面应该是这样的,

<div ng-controller="testController">
    <h1 class="myAnimateClass" ng-show="heading.show" ng-class="{fadeIn : heading.fadeInModel==true, fadeOut : heading.fadeOutModel}"> {{parent.activeChild}} </h1>
    <p ng-repeat="child in parent.children" ng-click="heading.show = false;changeHeading(child)">{{child}}</p>
</div>

我已经使用CSS3来实现淡入和淡出动画,

.myAnimateClass {
    -webkit-transition: opacity 1s ease-in-out;
    -moz-transition: opacity 1s ease-in-out;
    -o-transition: opacity 1s ease-in-out;
    -ms-transition: opacity 1s ease-in-out;
    transition: opacity 1s ease-in-out;
    opacity:1;
}

.myAnimateClass.ng-hide {
    opacity: 0;
}

<强>解释

为了达到您的要求,我在angularJS中使用了ng-class$timeout

你可以看到,我只有一个<h1>标签来显示你的标题。当我更改标题时,我只需更改它的绑定属性$scope.parent.activeChild

我使用了两个范围变量$scope.heading.fadeOutModel$scope.heading.fadeInModel来动态添加和删除类fadeInfadeOut

当用户点击更改标题时,我已将类fadeOut添加到您的标题中。所以,这将显示淡出的动画。我也在app.js中发起了一个函数,changeHeading()

你可以看到,我强迫角度等待1000毫秒来完成淡出动画。在此之后,它会将所选标题替换为新标题并添加类fadeIn。因此,它将启动淡入动画。

希望这会对你有帮助!!!

答案 1 :(得分:4)

根据选择显示特定元素的更有效的方法是使用ngSwitch。该指令用于根据范围表达式有条件地交换模板上的DOM结构。这是example

<强> HTML

<button ng-repeat="item in items" ng-click="parent.selection = item">{{ item }}</button>
<div class="animate-switch-container" ng-switch on="parent.selection">
  <div class="animate-switch" ng-switch-when="foo">foo</div>
  <div class="animate-switch" ng-switch-when="bar">bar</div>
</div>

<强>的Javascript

$scope.items = ['foo', 'bar'];
$scope.parent = {
  selection: $scope.items[0]
}

<强> CSS

.animate-switch-container {
  position:relative;
  height:40px;
  overflow:hidden;
}
.animate-switch {
  padding:10px;

}
.animate-switch.ng-animate {
  transition:opacity 1s ease;

}
.animate-switch.ng-leave.ng-leave-active,
.animate-switch.ng-enter {
  opacity: 0;
}
.animate-switch.ng-leave,
.animate-switch.ng-enter.ng-enter-active {
  opacity: 1;
}

这不是链接,但它是一个直接使用Angular的ngAnimate模块的工作动画。这里还有一个example of it on angular's website

答案 2 :(得分:4)

您可以使用.animation来定义基于Javascript的动画。例如,您定义的函数为addClassremoveClass

的值
app.animation('.fade', [function() {
    return {
        addClass: function(element, className, doneFn) {
        },
        removeClass: function(element, className, doneFn) {
        }
    };
}]);
Angular在检测到您正在从其中一个方法中添加或删除某个类时调用

  • {{ }}在模板中插值。例如。 <span class="{{shouldFade ? 'fade' : ''}}">....
  • 在模板中使用ng-class。例如。 <span ng-class="{fade: shouldFade}">...
  • 在指令中使用$animate服务。例如。 $animate.addClass(element, 'fade')$animate.removeClass(element, 'fade')
  

什么是className?这是我想要在淡入/淡出时应用的课程吗?我期待的课程?

在此示例中,它将是fade。这有点奇怪,因为在示例中已经清楚这是涉及的类名。但是,如果在同一个摘要周期中,您将多个类添加到同一个元素,则它们的串联将作为此字符串传递。

  

WhatFn意味着什么?我假设这是一个动画完成后运行的功能?那里有什么?

无论您定义的Javascript动画是什么,它都是调用的函数。例如,要定义一个不执行任何操作的动画:

addClass: function(element, className, doneFn) {
  doneFn();
},

调用它告诉Angular动画已完成。除其他外,这将从元素中删除ng-animate类。

  

如果我已经有一个doneFn,我该怎么做addClass和removeClass函数?

你输入了一些代码,可能是使用超时或第三方库,以某种方式更改元素。完成后,请致电doneFn。例如,1步不透明度“动画”:

addClass: function(element, className, doneFn) {
  element.css('opacity', 0.5);
  setTimeout(function() {
    doneFn();
  }, 1000);
},
  

我想直接使用Angular的ngAnimate模块生成一个工作动画,使用CSS或JS。

这与上面的答案没有太大关系!如果我在做一个真实案例,我强烈怀疑我会绝对定位元素,至少其他任何东西(我能想到的)都有点过于复杂。

但是,如果您确实想要使用ngAnimate链接动画,一种可能的方法是使用$animate.addClass$animate.removeClass在完成时返回一个承诺的事实。为了链接到隐藏元素时返回的这种承诺的结尾,必须从某种中心位置调用它,并跟踪哪个元素可见,被隐藏和被显示。

这样做的一种方法是使用2个自定义指令。一个将显示和隐藏每个元素,可以像ngShow一样使用。另一个将是一个父指令,它将允许任何时候只能看到一个元素,并在添加ng-hide之后删除ng-hide类(以及相关的动画)。指令必须通信,可以称为ngShowUniquengShowUniqueController,如下例所示。

<div ng-show-unique-controller>
  <h1 class="fade" ng-repeat="child in parent.children" ng-show-unique="parent.activeChild == child">@{{child.title}}</h1>
</div>

它们可以如下实现。

app.directive('ngShowUniqueController', function($q, $animate) {
  return {
    controller: function($scope, $element) {
      var elements = [];
      var expressions = [];
      var watchers = [];
      var unregisterWatchers = null;
      var visibleElement = null;

      function registerWatchers() {
        unregisterWatchers = $scope.$watchGroup(expressions, function(vals) {
          var newCurrentIndex = vals.indexOf(true);
          var addPromise;

          if (visibleElement) {
            // Set a fixed height, as there is a brief interval between
            // removal of this class and addition of another
            $element.css('height', $element[0].getBoundingClientRect().height + 'px'); 
            addPromise = $animate.addClass(visibleElement, 'ng-hide');
          } else {
            addPromise = $q.when();
          }
          visibleElement = elements[newCurrentIndex] || null;
          if (!visibleElement) return;

          addPromise.then(function() {
            if (visibleElement) {
              $animate.removeClass(visibleElement, 'ng-hide').then(function() {
                $element.css('height', '');
              });
            }
          })
        });
      }   

      this.register = function(element, expression) {
        if (unregisterWatchers) unregisterWatchers();
        elements.push(element[0]);
        expressions.push(expression);
        registerWatchers();

        // Hide elements initially
        $animate.addClass(element, 'ng-hide');
      };

      this.unregister = function(element) {
        if (unregisterWatchers) unregisterWatchers();
        var index = elements.indexOf(element[0]);
        if (index > -1) {
          elements.splice(index, 1);
          expressions.splice(index, 1);
        }
        registerWatchers();
      };
    } 
  };
});

app.directive('ngShowUnique', function($animate) {
  return {
    require: '^ngShowUniqueController',
    link: function(scope, element, attrs, ngShowUniqueController) {
      ngShowUniqueController.register(element, function() {
        return scope.$eval(attrs.ngShowUnique);
      });

      scope.$on('$destroy', function() {
        ngShowUniqueController.unregister(element);
      });
    }
  };
});

这可以在http://plnkr.co/edit/1eJUou4UaH6bnAN0nJn7?p=preview看到。我不得不承认,这一切都有点过时了。

答案 3 :(得分:1)

使用 ngRepeat只显示一个元素,在我看来,这是一个坏主意...因为你只显示一个元素! 你可以直接使用parent.activeChild属性......

看看下面的内容:

注意:我在十分钟内完成了这个片段,它没有经过优化,可能会有一些错误......你可以将它用作启动器:)

receiver
(function(window, angular, APP) {
  APP
    .value('menuObject', {
      name: 'Main Navigation',
      current: null,
      children: [{
        label: 'Don\'t ng-show element until ng-hide CSS transition is complete?',
        url: 'http://stackoverflow.com/questions/33336249/dont-ng-show-element-until-ng-hide-css-transition-is-complete',
        isCurrent: false
      },
      {
        label: 'Hitmands - Linkedin',
        url: 'http://it.linkedin.com/in/giuseppemandato',
        isCurrent: false
      },
      {
        label: 'Hitmands - Github',
        url: 'https://github.com/hitmands',
        isCurrent: false
      },
      {
        label: 'Hitmands - StackOverflow',
        url: 'http://stackoverflow.com/users/4099454/hitmands',
        isCurrent: false
      }
  ]})
  .directive('menu', function(menuObject, $q) {
    function menuCtrl($scope, $element) {
      $scope.parent = menuObject;
      
      this.getCurrentChild = function() {
        return $scope.parent.current;
      };
      this.getDomContext = function() {
        return $element;
      };
      this.setCurrentChild = function(child) {
        return $q.when($scope.parent)
        .then(function(parent) {
          parent.current = child;
          return parent;
        })
        .then(function(parent) {
          return parent.children.forEach(function(item) {
            item.isCurrent = child && (item.label === child.label);
          });
        })
      };
    }
    
    return {
      restrict: 'A',
      templateUrl: 'embedded-menutemplate',
      scope: {},
      controller: menuCtrl
    };
  })
  .directive('menuItem', function($animate, $q, $timeout) {
    
    function menuItemPostLink(iScope, iElement, iAttributes, menuCtrl) {
      iElement.bind('click', setCurrentTitle);
      iScope.$on('$destroy', function() {
        iElement.unbind('click', setCurrentTitle);
      })
      
      function setCurrentTitle(event) {
        event.preventDefault();
        var title;
        
        return $q
        .when(menuCtrl.getDomContext())
        .then(function(_menuElement) {
          title = angular.element(
            _menuElement[0].querySelector('#menuItemCurrent')
          );
        })
        .then(function() {
          return title.addClass('fade-out');
        })
        .then(function() {
          return $timeout(menuCtrl.setCurrentChild, 700, true, iScope.child);
        })
        .then(function() {
          return title.removeClass('fade-out');
        })
      }
    }
				
    return {
      require: '^menu',
      link: menuItemPostLink,
      restrict: 'A'
    };
  })
;

})(window, window.angular, window.angular.module('AngularAnimationExample', ['ngAnimate']));
nav {
		text-align: center;
	}
	.link {
		display: inline-block;
		background-color: lightseagreen;
		color: black;
		padding: 5px 15px;
		margin: 1em;
	}
	#menuItemCurrent {
		padding: 1em;
		text-transform: uppercase;
		border: 1px solid black;
	}
	#menuItemCurrent span {
		transition: 500ms opacity linear;
		opacity: 1;
	}
	#menuItemCurrent.fade-out span {
		opacity: 0;
	}

答案 4 :(得分:1)

问题是H1是位于其父级内的块级元素,不允许重叠。这就是为什么你看到一个正在消失的动画按下正在出现的动画。

您可以在此处看到这种情况更加清晰:Demo

要解决此问题,您需要保持块级元素H1,并使其位置相对,以便它可以保持其在页面整体流中的相对位置。然后将子SPAN元素设置为具有绝对定位 - 相对于父H1的绝对位置。这允许所有span元素相互重叠。

<强> CSS

.fade {
  opacity: 1;
  position: relative;
}

.fade.ng-hide-add {
    transition:opacity 1s ease;
    position: absolute;
}

.fade.ng-hide-remove {
    transition:opacity 1s ease 1s;
    position: absolute;
}

.fade.ng-hide-add {
  opacity:1;
}

.fade.ng-hide-add.ng-hide-add-active {
  opacity:0;
}

.fade.ng-hide-remove {
    opacity:0;
}

.fade.ng-hide-remove.ng-hide-remove-active {
    opacity:1;
}

<强> HTML

  <body ng-controller="MainCtrl">
    <h1><span class="fade" ng-repeat="child in parent.children" ng-show="parent.activeChild == child ">@{{child.title}}</span></h1>
    <button ng-repeat="child in parent.children" ng-click="parent.activeChild = child">{{ child.title }}</button>
  </body>

虽然存在一个问题...由于SPAN元素具有绝对定位,因此在动画时将其从流中移除,并且父H1无法调整大小以适合SPAN内容。这会导致SPAN意外跳转。

解决这个问题的方法(不可否认,它有点像黑客攻击)是在SPAN转发器之后添加一个空白区域。因此,当ngRepeat SPANS因绝对定位而退出正常流量时,ngRepeat外部的空白空间会保留H1的间距。

这是一个有效的Plunker

答案 5 :(得分:1)

您可能希望查看所有现代浏览器都支持的transitionend事件。

element.addEventListener('transitionend', callback, false);

答案 6 :(得分:0)

快速回答这个问题 - 为了解决这个问题,我一直把内容放在绝对的位置。这样,当转换发生时,它会保持在相同的位置。

没有别的方法可以解决这个问题,因为如果内容inlineinline-block,内容会占用dom中的空间,这就是为什么你会看到跳转直到转换完成的原因