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
类中定义的过渡告诉浏览器在触发 hover
和 mouseleave
事件时如何在这些状态之间进行过渡。
我们最终得到的效果如下
基本 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 而不必依赖 $animate
和 ngAnimate
的示例。
这样做的结果是,当我们通过单击复选框更改底层布尔值时,严格由数据更改触发的动画。
Angular 数据驱动的 CSS3 过渡
$animate
进行过渡
使用 如果我们想利用 CSS3 过渡和 $animate
服务,那么我们需要了解一些关于 $animate
在幕后如何工作的信息。
$animate
服务支持几个内置于 Angular 的指令。 这无需任何其他配置即可使用,并允许我们用纯 CSS 为我们的指令创建动画。 要以这种方式使用动画,您甚至不需要在控制器中包含 $animate
;只需将 ngAnimate
作为 Angular 模块的依赖项即可。
将 ngAnimate
包含在您的模块中后,Angular 处理某些内置指令的方式就会发生变化。 Angular 将开始挂接到这些指令并监控它们,并在触发某些事件时向元素添加特殊类。 例如,当您添加、移动或从 ngRepeat
指令正在使用的数组中删除项目时,Angular 现在将捕获该事件,并向 ngRepeat
中的该元素添加一系列类。
在这里,您可以看到 ngAnimate
在 ngRepeat
的进入事件上添加的类

ngRepeat
事件类附加的 CSS 类采用 ng-{EVENT}
和 ng-{EVENT}-active
的形式,用于结构事件(如进入、移动或离开)。 但是,对于基于类的动画,它采用 {CLASS}-add
、{CLASS}-add-active
、{CLASS}-remove
和 {CLASS}-remove-active
的形式。 这些规则的例外是 ng-hide
和 ng-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;
}
}
在这里,我们创建了一个 appear
和 disappear
动画,可以通过 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
参数只是您要定位的类,而动画函数可以是一个匿名函数,当它被调用时,它将接收 element
和 done
参数。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
进行动画的另一个好处是,一旦您为该指令编写了动画,就可以很好地打包并将它们轻松地移动到其他项目中。在我看来,这始终是一件好事。
叫我吹毛求疵的人也行,但我希望在浏览网站时没有动画。我不需要菜单在我点击展开时向下滑动;只需要立即出现。我喜欢速度和效率。
我认为我同意你的观点,动画不应该仅仅是为了动画而存在,也不应该减慢 UI 的导航速度。这并不意味着我认为动画在 UI 中没有位置。
我认为此示例中的删除过渡是一个不必要的动画的良好示例。它很慢,并且并没有真正增强用户体验,因为用户直接与正在使用的元素进行交互。
但是,将底部项目移动到顶部的示例是动画改善用户体验的一个很好的例子。如果您单击按钮并且底部项目突然从列表底部消失并在顶部重新出现,则更改不会像使用细微动画那样明显(在我看来,动画可以更快一点)。每当我在界面中使用动画时,我都会默认为 0.1 秒,因为它恰好足以显示过渡,同时仍然具有“即时”的感觉。按照惯例,我将 UI 元素的最大动画时间设为 0.25 秒。如果时间更长,用户就会等待。
我认为 neil 在大多数情况下也是正确的。如果您曾经感觉到“哎呀,动画”,那么就不应该有那个动画。如果您没有注意到它,或者注意到它,以至于它令人愉悦或帮助您理解刚刚发生的事情,那么它可能是值得的。
我认为这些都是很好的例子:https://css-tricks.cn/transitional-interfaces-coded/
这就是为什么我的动画或过渡效果持续时间永远不会超过200毫秒(即使这样也有些勉强)。我认为,微妙是关键。用户不会感觉他们在等待UI元素与其交互,但他们仍然会在交互之前看到一些令人愉悦的东西。
Neil,你真是个一丝不苟的人!!!;)
虽然几乎每个人都会同意过多的动画不好,但有意义的动画是现代网页设计中一个非常有用的工具。
零动画意味着UI状态之间的静态切换。
http://www.google.com/design/spec/animation/authentic-motion.html
仍然不明白为什么要使用这么复杂的东西——jQuery有什么问题吗(我的意思是,除了有一个被过度炒作的框架叫做Angular,所以我们现在必须使用Angular)?!??
我相信在jQuery的早期也有人说过同样的话:D
在你的网站中使用Angular动画的唯一原因是你已经在使用AngularJS,并且不想将其与jQuery混合使用。
jQuery没有问题。如果你正在使用AngularJS,可以使用它。
我遇到一个奇怪的问题……每次我尝试在我的服务器上运行这段JS时,它都能完美运行,直到我尝试将“芥末”项移动到顶部……
“将项目移动到顶部”按钮在大多数情况下都能按预期工作,但当它到达芥末时,它似乎用芥末替换了所有其他项目,直到全是芥末,而且芥末永远不会消失。我不想有这么多芥末,我做错了什么?
代码示例
芥末永远不会嫌多。
动画真正带来改进的组件很少——而且在大多数情况下,这些组件都与触发伪类的事件绑定,例如:hover 或:focus,因此你可以在没有 Angular 的情况下实现它们。
根据我的经验,动画的最长可行持续时间在0.1到0.3秒之间,具体取决于大小,否则会感觉迟钝。如果你不想花时间微调动画,最好使用较短的持续时间,如果有时间再回来调整它。
好吧,有时动画也会吸引“其他人”。也许一点点动画,但不要“太多”。
很棒的指南!它非常出色地描述了 ngAnimate *实际上并没有做任何动画*。我太爱 AngularJS 了。
我喜欢这种动画,当它很快并且不太激进时。感谢你的解释,Ben。
我们刚刚开始尝试使用 Google 的 Material Design Angular 集成来进行动画。似乎在实现 Angular/Material 中有很多好处,到目前为止,它似乎是一种修改动画的更简单方法。
你用于 @keyframes 动画的 CodePen 似乎与前面的示例相同。
也许你成为了 Chrome 后退按钮 iframe 交换错误 的受害者?
嗨,Chris——顺便说一句,我非常喜欢你的网站,它绝对是这类资料中最有用的网站之一。
以前从未听说过这个错误,但由于我是在新版 Internet Explorer(又名 Safari)中查看它,并且它是在新标签页中打开的,所以我怀疑这不是原因。
我假设你可以看到 CodePen 中的 @keyframes,并且我以某种方式搞砸了它。我确实在本地用 @keyframes 重新创建了 CodePen,并且它工作得很好,所以没问题。
@Ben——很棒的教程。感谢你以及你(至少我认为是你)之前做的关于 Angular 数据可视化的另一个教程。很棒的东西!