【问题标题】:Don't ng-show element until ng-hide CSS transition is complete?在 ng-hide CSS 过渡完成之前不要 ng-show 元素?
【发布时间】:2016-01-24 23:47:58
【问题描述】:

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

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

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

我的工作很粗略,只使用了 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)

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

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

app.animation('.fade', [function() {
    return {
        addClass: function(element, className, doneFn) {
        },
        removeClass: function(element, className, doneFn) {
        }
    };
}]);
  • 什么是className?是我想在淡入/淡出时申请的课程吗?我期待的课程?
  • doneFn 是什么意思?我假设它是动画完成后运行的函数?里面有什么?
  • 如果我已经有一个doneFn,我应该在addClassremoveClass 函数中做什么?

目标

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

【问题讨论】:

  • 您可以尝试向.ng-enter 过渡CSS 添加延迟,该延迟与.ng-leave 的淡出过渡时间相同,例如transition: opacity .5s .5s。应该不需要为此编写任何 JS
  • 这不会导致第二个元素先出现,然后第一个元素消失吗?我不想同时看到这两个元素。即使不透明度为0,元素仍然会占用空间,是吗?
  • 为什么不构建一个角度控制器来管理这些东西?
  • 因为我不知道该怎么做。
  • 你能用当前问题创建 jsfiddle。

标签: javascript angularjs animation


【解决方案1】:

对此的快速回答 - 过去为了解决这个问题,我总是将内容定位为绝对。这样,当发生过渡时,它会保持在相同的位置。

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      问题在于 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

      【讨论】:

        【解决方案4】:

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

        看看下面的内容:

        注意:我只用了十分钟就完成了这个 sn-p,它没有经过优化并且可能有一些错误......你可以将它用作启动器 :)

        (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;
        	}
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular-animate.js"></script>
        
        <article ng-app="AngularAnimationExample">
          
          <nav menu></nav>
        
          <script id="embedded-menutemplate" type="text/ng-template">
        
        	<nav >
        		<a menu-item class="link" ng-repeat="child in parent.children track by $index" ng-bind="child.label"  ng-href="{{ child.url }}"></a>
        	
        		<h1 id="menuItemCurrent"><span  ng-bind="parent.current.url || 'NoMenuCurrentSelected'"></span></h1>	
        		{{ parent.current || json }}
        	</nav>
        	
          </script>
        </article>

        【讨论】:

          【解决方案5】:

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

          app.animation('.fade', [function() {
              return {
                  addClass: function(element, className, doneFn) {
                  },
                  removeClass: function(element, className, doneFn) {
                  }
              };
          }]);
          

          当 Angular 检测到您正在通过以下方法之一从元素中添加或删除类时调用:

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

          什么是类名?是我想在淡入/淡出时申请的课程吗?我期待的课程?

          在本例中为fade。诚然,这有点奇怪,因为在示例中已经很清楚这是所涉及的类名。但是,如果在同一个摘要循环中您将多个类添加到同一个元素,那么它们的连接将作为此字符串传递。

          doneFn 是什么意思?我假设它是动画完成后运行的函数?里面有什么?

          这是一个在你定义的任何Javascript动画完成后调用的函数。例如,定义一个什么都不做的动画:

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

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

          如果我已经有 doneFn,那么我应该在 addClass 和 removeClass 函数中做什么?

          您在它们中放入一些代码,可能使用超时或 3rd 方库,以某种方式更改元素。完成后,您致电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 看到。我不得不承认,这一切都有些古怪。

          【讨论】:

          • 许多其他好的答案(从最佳实践的角度来看,有些可能更好),但您直接回答了我的问题并产生了可用的结果。谢谢。
          【解决方案6】:

          根据选择显示特定元素的更 ng-animate 方式是使用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

          【讨论】:

          • 遇到了同样的问题,这对我来说是完美的解决方案
          【解决方案7】:

          为什么要为每个标题使用单独的&lt;h1&gt;。您可以使用单个 &lt;h1&gt; 标记来显示您的标题。

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

          更新

          注意,代码被编辑为使用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

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

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

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

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

          希望对你有帮助!!!

          【讨论】:

          • 我很欣赏这个答案,但我真的不是在这里寻找“解决方法”。我想有一个示例演示直接使用 Angular 的动画模块来实现这一点。我的问题应该更清楚。也许你能提供一个答案?
          • @EchoLogic,嗯。我已经更新了我的答案。现在没有更新我的解释。
          猜你喜欢
          • 2015-04-24
          • 2017-11-09
          • 1970-01-01
          • 1970-01-01
          • 2014-12-08
          • 2014-03-27
          • 1970-01-01
          • 2014-12-30
          • 1970-01-01
          相关资源
          最近更新 更多