【问题标题】:How can I create an AngularJS directive to animate an element on data change?如何创建 AngularJS 指令以在数据更改时为元素设置动画?
【发布时间】:2014-06-13 22:57:15
【问题描述】:

在我正在构建的应用程序中,我在屏幕上显示了一个标题。它绑定到我范围内的一个属性。我正在使用ng-bind-html 来简化构建标题字符串的方式。 HTML 看起来像这样:

<div class="headline slide-and-fade-up" animate-on-change="headline" ng-bind-html="headline"></div>

animate-change 是我正在尝试编写的指令,它将导致标题在更改时进行动画处理。请求的动画是让旧动画向上移动并离开屏幕,而新动画则从底部向上移动并到位。我已经构建了一个与ng-animate 兼容的动画,称为slide-and-fade-up,它可以完成我想要它做的事情。由于我需要在新值出现时对旧值进行动画处理,因此我需要复制元素并在 DOM 中拥有一个旧值和一个新值,然后让旧值离开。

到目前为止,我的指令如下所示:

angular.module('myApp.directives').directive('animateOnChange', ['$animate',
    function($animate) {
        return {
            restrict: 'A',
            link: function(scope, element, attrs) {
                scope.$watch(attrs.animateOnChange, function(newValue, oldValue) {
                    if (newValue !== undefined && oldValue !== undefined && newValue !== oldValue) {
                        $animate.enter(element.clone(), element.parent(), element);
                        $animate.leave(element);
                    }
                });
            }
        }
    }
]);

这有几个问题:

  1. 克隆的元素似乎没有“实时”指令,即它存在于 DOM 中,但实际上并没有触发此代码,这意味着动画只触发一次,然后再也不会触发
  2. 进入的元素中包含旧值,离开的元素中包含新值。我不知道为什么会这样,但它基本上完全落后

我怎样才能完成这个指令,让它做我想做的事?我确实通过使用ng-if 并观察数据更改并打开布尔值,等待一小段时间然后重新打开它来让它工作。这行得通,但与带有$animate 钩子的自包含指令相比,对我来说似乎过于hacky。感谢您的帮助。

【问题讨论】:

  • 你能发布一个链接到一个简化的 Plunker/Fiddle 来演示这个代码吗?
  • 非常基本的示例,但这是我现在拥有的损坏代码:codepen.io/nseegmiller/pen/blyxI

标签: angularjs animation angularjs-directive ng-animate


【解决方案1】:

编译您传递给$animate.enter 的克隆内容,然后在调用$animate.leave 之前将原始元素的innerHTML 设置为作为参数提供给$watch 的“oldValue”:

.directive('animateOnChange', function($animate, $compile) {
  var watchers = {};
  return {
    restrict: 'A',
    link: function(scope, element, attrs) {
      // deregister `$watch`er if one already exists
      watchers[scope.$id] && watchers[scope.$id]();  
      watchers[scope.$id] = scope.$watch(attrs.animateOnChange, function(newValue, oldValue) {          
        if (newValue !== oldValue) {
          $animate.enter($compile(element.clone())(scope), element.parent(), element);
          element.html(oldValue);
          $animate.leave(element); 
        }
      });
    }
  }
})

注意:由于每次满足 $watch 表达式更改测试时都会重新编译和链接指令,因此您需要考虑到每次都会创建 new 手表这一事实.

在我的示例中,我将注销函数存储在任何已编译指令实例中可用的对象中,并在创建除第一个之外的任何新观察者之前调用它。如果你不这样做,每次编译指令时你都会看到双倍的观察者......显然不是一件好事。

Fork of your JS Bin

【讨论】:

    【解决方案2】:

    链接函数在元素最初放置在页面上时运行,在其编译阶段。

    $animate.leave() 将在完成后从 DOM 中删除离开元素,这是您附加行为的元素。这可能就是它只发生一次的原因。

    对于初学者,您需要引入一个变量来存储“当前”元素,然后在每次转换时对其进行管理。

    现在的问题是调用 element.clone() 是有问题的。 Angular 的所有优点都不是魔法附在它上面的。这是在监视阶段运行的,这意味着范围(数据模型)已更改,但 DOM 可能尚未更新。所以你在 Angular 更新它之前克隆元素,克隆变得“愚蠢”。那就是您看到输入的具有旧值的那个。

    与此同时,在动画开始后的一瞬间,Angular 赶上并将元素(现在正在退出)更新为新值,就在它被销毁之前。

    您可能需要承担渲染内容的全部责任,而不是使用 ng-bind-html。只需获取您正在创建的元素并调用 element.html(newValue),因为您正在观看该表达式,因此您已经拥有该元素。或者,您可以让克隆(哑)元素成为离开的元素,并允许 Angularified 元素进入,假设当您开始动画时该值将“现在很快”发生变化。

    【讨论】:

      猜你喜欢
      • 2014-03-09
      • 1970-01-01
      • 2017-05-20
      • 2015-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-03
      相关资源
      最近更新 更多