Angular 动画:Angular 的方式

Avatar of Benjamin Simmons
Benjamin Simmons 发布

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费额度!

AngularJS 是 JavaScript MVW 框架世界中的主要参与者。 “用 Angular 的思维方式思考”对于来自 jQuery 或其他 DOM 操作繁重的库的开发者来说可能难以捉摸。 有一种 “Angular 方式” 来处理事情,这种方式是数据驱动的,而不是使用 DOM 遍历来驱动视图更改,在动画等方面可能难以可视化。 在一起,我们将详细了解如何使用 Angular 团队提供的工具进行动画制作。

ngAnimate$animate 服务

Angular 核心团队为我们提供了 ngAnimate 模块,以便我们可以为我们的应用程序提供一种从数据驱动的 “Angular 方式” 进行动画的方式,并且以便我们可以挂接到 Angular 通过其一些内置指令发出的事件。

Angular 与 jQuery 不同,它专注于通过使用控制器将我们的视图绑定到 JavaScript 对象。 这种方法允许我们将视图值(如输入字段)直接绑定到 JavaScript 对象中的对应值,并通过数据更改触发视图更改,反之亦然。

那么,如果这些事件可能来自视图或相应的对象被更改,我们如何将动画挂接到这些事件呢?

首先,我们需要将 ngAnimate 添加到我们的项目中。

在项目中包含 Angular 动画

从 1.2.0 开始,动画不再是 Angular 核心的部分,而是位于其自己的独立模块中:ngAnimate。 为了使用 $animate 服务,我们需要在 HTML 文件中 Angular 之后包含动画库,如下所示:js/lib/angular-animate.js

或者,您也可以使用 CDN 或 bower 安装 angular-animate

$ bower install --save angular-animate

无论您选择哪种安装方式,请确保将其包含在您的源文件中,如下所示

<script src="js/lib/angular.js"></script>
<script src="js/lib/angular-animate.js"></script>

接下来,我们必须将 ngAnimate 模块作为我们应用程序的依赖项。 这可以在我们实例化 Angular 应用程序时完成,如下所示

angular.module('myApp', ['ngAnimate']);

现在 ngAnimate 已包含在我们的项目中(并且只要我们的控制台中没有注入器错误或类似错误),我们就可以开始使用 Angular 创建动画了!

CSS3 过渡

在任何应用程序中包含动画的最简单方法是使用 CSS3 过渡。 这是因为它们完全基于类,这意味着动画在类中定义,并且只要我们在 HTML 中使用该类,动画就会在浏览器中工作。

CSS 过渡 是允许 HTML 元素从一种样式平稳更改为另一种样式的动画。 要定义过渡,我们需要指定我们要添加效果的元素以及该效果的持续时间。

首先,让我们看一个 CSS3 过渡的简单示例,然后我们可以了解如何从数据驱动的 Angular 应用程序中利用这些知识。

让我们在一个容器 div 内创建一个简单的 div 并为其应用两个类:一个用于基本样式,另一个用于我们的过渡。

<div class="container">
  <div class="box rotate"></div>
</div>

现在我们可以为元素的悬停状态或静态状态添加过渡

.box {
  margin: 50px auto;
  background: #5FCF80;
  width: 150px;
  height: 150px;
}
.box:hover {
  transform: rotate(360deg);
  background: #9351A6;
  border-radius: 50%;
}
.rotate {
  transition: all 0.5s ease-in-out;
}
.rotate:hover {
  transition: all 1s ease-in-out;
}

这将两个状态应用于我们的 div:一个正常状态和一个当我们悬停在 div 上时的状态。 在 .rotate.rotate:hover 类中定义的过渡告诉浏览器在触发 hovermouseleave 事件时如何在这些状态之间进行过渡。

我们最终得到的效果如下

基本 CSS3 过渡

Angular 数据驱动的 CSS3 动画

现在让我们看看如何在 Angular 应用程序中执行类似的操作,并将此相同的功能绑定到应用程序中的某些数据。

与其在 :hover 上执行此过渡,我们可以通过将过渡绑定到一个类 .rotate 并为 div 的 “box” 和 “circle” 状态创建类来创建一个简单的动画。 这使我们能够使用 Angular 中内置的 ng-class 指令在类之间切换。

.box {
  margin: 20px auto;
  background: #5FCF80;
  width: 150px;
  height: 150px;
}
.circle {
  transform: rotate(360deg);
  background: #9351A6;
  border-radius: 50%;
  margin: 20px auto;
  width: 150px;
  height: 150px;
}
.rotate {
  transition: all 1s ease-in-out;
}

为此,我们需要设置我们的 Angular 应用程序并在 ng-class 指令中创建一个条件语句,以根据 $scope 上布尔值的值切换类。

<div ng-app="myApp" ng-controller="MainCtrl">
  <div class="container">
    <input type="checkbox" ng-model="boxClass" />
    <div class="box rotate" ng-class="{'box': boxClass, 'circle': !boxClass} "></div>
  </div>
</div>

现在让我们设置我们的 JavaScript

angular.module('myApp', [])
.controller('MainCtrl', function($scope) {
  $scope.boxClass = true;
});

在这里,我们将附加到 $scope.boxClass 的布尔值绑定到元素是否应该具有 .box.circle 类。 如果布尔值为真,则元素将具有 .box 类。 如果为假,它将具有 .circle 类。 这允许我们通过更改数据的价值来触发 CSS3 过渡,而无需任何 DOM 操作。

这没有使用 $animate 服务,但我希望提供一个您可以单独使用 CSS3 而不必依赖 $animatengAnimate 的示例。

这样做的结果是,当我们通过单击复选框更改底层布尔值时,严格由数据更改触发的动画。

Angular 数据驱动的 CSS3 过渡

使用 $animate 进行过渡

如果我们想利用 CSS3 过渡和 $animate 服务,那么我们需要了解一些关于 $animate 在幕后如何工作的信息。

$animate 服务支持几个内置于 Angular 的指令。 这无需任何其他配置即可使用,并允许我们用纯 CSS 为我们的指令创建动画。 要以这种方式使用动画,您甚至不需要在控制器中包含 $animate;只需将 ngAnimate 作为 Angular 模块的依赖项即可。

ngAnimate 包含在您的模块中后,Angular 处理某些内置指令的方式就会发生变化。 Angular 将开始挂接到这些指令并监控它们,并在触发某些事件时向元素添加特殊类。 例如,当您添加、移动或从 ngRepeat 指令正在使用的数组中删除项目时,Angular 现在将捕获该事件,并向 ngRepeat 中的该元素添加一系列类。

在这里,您可以看到 ngAnimatengRepeat 的进入事件上添加的类

ngRepeat 事件类

附加的 CSS 类采用 ng-{EVENT}ng-{EVENT}-active 的形式,用于结构事件(如进入、移动或离开)。 但是,对于基于类的动画,它采用 {CLASS}-add{CLASS}-add-active{CLASS}-remove{CLASS}-remove-active 的形式。 这些规则的例外是 ng-hideng-show。 这两个指令都具有触发的添加和删除事件,就像 ng-class 一样,但它们都共享 .ng-hide 类,该类在适当的时候添加或删除。 您还会看到 ngAnimate 在某些指令的动画上添加 .ng-animate 类。

下表说明了一些内置指令、触发的事件以及在将 ngAnimate 添加到项目时临时添加的类

内置指令的 $animate 事件

指令 事件
ngRepeat 进入 ng-enter, ng-enter-active
离开 ng-leave, ng-leave-active
移动 ng-move, ng-move-active
ngView, ngInclude, ngSwitch, ngIf 进入 ng-enter, ng-enter-active
离开 ng-leave, ng-leave-active
ngClass 添加 ng-add, ng-add-active
移除 ng-remove, ng-remove-active
ngShow, ngHide 添加, 移除 ng-hide

当动画触发时,Angular 会自动检测 CSS 是否附加到动画,并在动画运行完成之前添加 .ng-{EVENT}-active 类。然后,它会从 DOM 中删除该类以及任何其他添加的类。

下面是一个使用 CSS3 过渡来动画化 ngRepeat 指令的示例。在其中,我们将过渡附加到基本类(在本例中为 .fade),然后利用 ngAnimate 在将元素添加到数组和从数组中删除元素时添加到 li 元素的类。再次,这允许我们以 Angular 的方式进行数据驱动的动画。

ngRepeat $animate 驱动的 CSS3 过渡

如我们所见,Angular 的 ngAnimate 使我们能够轻松地利用事件并利用 CSS3 过渡的功能,在我们指令上实现一些非常酷、自然的动画。这是我们为 Angular 应用进行动画的最简单方法,但现在我们将看看一些更复杂的选择。

CSS3 动画

CSS3 动画比过渡更复杂,但在 ngAnimate 方面具有许多相同的实现。但是,在 CSS 中,我们将使用 @keyframes 规则来定义我们的动画。这与我们之前进行基本过渡的方式非常相似,只是我们在 CSS 中使用 animation 关键字并为动画指定一个名称,例如

@keyframes appear {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes disappear {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

在这里,我们创建了一个 appeardisappear 动画,可以通过 CSS 在我们之前过渡的位置触发。

.fade.ng-enter {
  animation: 2s appear;
}

.fade.ng-leave {
  animation: 1s disappear;
}

这次的不同之处在于,如您所见,我们不再需要使用 .ng-enter-active.ng-leave-active,而是可以将动画附加到 .ng-leave.ng-active,并且由于 ngAnimate,动画将在适当的时间触发。这与我们上面提到的过渡方法相比,并不是一种特别更好的方法,但它说明了如何使用 CSS3 动画,而 CSS3 动画的功能可以比这个简单的效果强大得多。

当此动画应用于我们之前的杂货清单 ngRepeat 示例时,最终结果将如下所示

ngRepeat $animate 驱动的 CSS3 动画

JavaScript 动画

现在来说说房间里的大象:使用 AngularJS 的 JavaScript 动画

Angular 完全由数据驱动,并具有其花哨的双向数据绑定——也就是说,直到它不是。这是从 jQuery 转到 Angular 时最令人困惑的事情之一。我们被告知要重新学习我们的思维方式,并抛弃 DOM 操作,转而支持绑定,但随后,在某个时候,他们又将它抛回给我们。好吧,欢迎来到这个循环点。

JavaScript 动画有一个主要优势——JavaScript 无处不在,并且比一些高级 CSS3 动画具有更广泛的接受度。现在,如果您只是针对现代浏览器,那么这可能不会成为问题,但如果您需要支持不支持 CSS 过渡的浏览器,那么您可以轻松地使用 Angular 注册 JavaScript 动画并在您的指令中反复使用它。基本上,JavaScript 在旧版浏览器中拥有更多支持,因此,JavaScript 动画也是如此。

当您将 ngAnimate 作为 Angular 模块的依赖项包含在内时,它会将 animation 方法添加到模块 API 中。这意味着您现在可以使用它来注册您的 JavaScript 动画并利用内置指令(如 ngRepeat)中的 Angular 钩子。此方法接受两个参数:className(字符串)animationFunction(函数)

className 参数只是您要定位的类,而动画函数可以是一个匿名函数,当它被调用时,它将接收 elementdone 参数。element 参数就是这样,元素作为 jqLite 对象,而 done 参数是一个函数,当您的动画完成运行时,您需要调用它,以便 angular 可以继续运行并知道事件已完成。

然而,这里需要掌握的主要内容是动画函数需要返回什么。Angular 将查找一个对象,该对象包含与您希望为该特定指令触发的事件名称匹配的键。如果您不确定指令支持什么,只需参考我上面的表格。

因此,对于我们的 ngRepeat 示例,它将如下所示

return {
  enter: function(element, done) {
    // Animation code goes here
    // Use done() in your animation callback
  },
  move: function(element, done) {
    // Animation code goes here
    // Use done() in your animation callback
  },
  leave: function(element, done) {
    // Animation code goes here
    // Use done() in your animation callback
  }
}

如果我们将所有这些与相同的老旧无聊(抱歉)的 ngRepeat 杂货清单示例结合起来,并使用 jQuery 进行实际动画

var app = angular.module('myApp', ['ngAnimate'])
.animation('.fade', function() {
  return {
    enter: function(element, done) {
      element.css('display', 'none');
      $(element).fadeIn(1000, function() {
        done();
      });
    },
    leave: function(element, done) {
      $(element).fadeOut(1000, function() {
        done();
      });
    },
    move: function(element, done) {
      element.css('display', 'none');
      $(element).slideDown(500, function() {
        done();
      });
    }
  }
})

现在,让我分解一下正在发生的事情。

我们可以删除之前在 .fade 类上设置的任何 CSS,但我们仍然需要某种类来注册动画。因此,为了保持连续性,我只是使用了旧的 .fade 类。

基本上,这里发生的事情是 Angular 将注册您的动画函数,并在该特定元素上发生该事件时在该元素上调用它们。例如,当新项目进入 ngRepeat 时,它将调用您的 enter 动画函数。

这些都是非常基本的 jQuery 动画,我不会在这里详细介绍,但值得注意的是,ngRepeat 会在将新项目添加到数组时自动将其添加到 DOM 中,并且该项目将立即可见。因此,如果您尝试使用 JavaScript 实现淡入效果,则需要在淡入之前立即将显示设置为 none。这是您可以通过 CSS 动画和过渡避免的事情。

让我们将所有内容整合在一起,看看我们得到了什么

ngRepeat $animate 驱动的 JavaScript 动画

结论

ngAnimate 模块的名字有点误导性。

当然,如果我尝试的话,我也想不出更好的名字,但它实际上并没有执行任何动画。相反,它为您提供了访问 Angular 事件循环的权限,以便您可以在正确的数据驱动点执行自己的 DOM 操作或 CSS3 动画。这本身就很强大,因为我们是以“Angular 的方式”来做这件事,而不是试图强迫我们自己的逻辑和时间安排到一个非常特殊的框架上。

使用 ngAnimate 进行动画的另一个好处是,一旦您为该指令编写了动画,就可以很好地打包并将它们轻松地移动到其他项目中。在我看来,这始终是一件好事。