应用动画
在这最后一步中,我们将通过在我们之前创建的模板代码的顶部添加CSS和JavaScript动画丰富我们的手机分类网站应用。
我们现在使用
ngAnimate
模拟以启用动画,以贯穿这个应用。我们还使用常用的
ng
指令以自动触发使动画接入的钩子。发现一个应用之后,动画将在标准DOM操作之间运行,该标准DOM操作在给定的时间内发布在元素上(例如,在
ngRepeat
上插入和移除节点,或在ngClass
上添加和移除类)。
把工作空间重置到第十二步
git checkout -f step-12
刷新你的浏览器或在线检查这一步:Step 12 Live Demo
下面列出了第十一步和第十二步之间最重要的区别。你可以在GitHub上看到完整的差异。
依赖性
Angular在ngAnimate
模块中提供动画功能,它与核心Angular框架分开发布。另外,我们将在项目中使用jquery
以实现额外的JavaScript动画。
我们正在使用Bower以安装客户侧依赖性。这一步更新了bower.json
配置文件,以包含新的依赖性:
{ "name": "angular-seed", "description": "A starter project for AngularJS", "version": "0.0.0", "homepage": "https://github.com/angular/angular-seed", "license": "MIT", "private": true, "dependencies": { "angular": "1.4.x", "angular-mocks": "1.4.x", "jquery": "~2.1.1", "bootstrap": "~3.1.1", "angular-route": "1.4.x", "angular-resource": "1.4.x", "angular-animate": "1.4.x" } }
"angular-animate": "1.4.x"
告诉bower安装一个angular-animate组件的版本,与v1.4x版兼容。"jquery": "~2.1.1"
告诉bower安装jQuery的v2.1.1版。注意这不是一个Angular库,它是标准jQuery库。我们可以使用bower来安装一个大作用域的第三方库。
我们必须要求bower以下载并安装依赖性运行以下指令实现它:
npm install
动画如何与ngAnimate
协作
要想知道动画如何与AngularJS协作,请先阅读?AngularJS动画指南。
模板
在HTML模板代码内部需要修改,以链接asset文件,它定义了动画以及angular-animate.js
文件。该动画模块,即ngAnimate
,被定义在angular-animate.js
内部,并包含了必要的代码,以使你的应用程序变得动感。
这里是在索引文件中需要修改的地方:
app/index.html
.
... <!-- for CSS Transitions and/or Keyframe Animations --> <link rel="stylesheet" href="css/animations.css"> ... <!-- jQuery is used for JavaScript animations (include this before angular.js) --> <script src="/attachments/image/wk/angularjs/jquery.js"></script> ... <!-- required module to enable animation support in AngularJS --> <script src="/attachments/image/wk/angularjs/angular-animate.js"></script> <!-- for JavaScript Animations --> <script src="/attachments/image/wk/angularjs/animations.js"></script>...
可以在CSS代码(animations.css
)内中创建动画,也可以在JavaScript代码(animations.js
)内部创建动画。但是在开始之前,让我们创建一个新模块,它使用ngAnimate模块,作为依赖性,就像我们之前用ngResource
所作的。
模块和动画
app/js/animations.js
.
angular.module('phonecatAnimations', ['ngAnimate']); // ... // this module will later be used to define animations // ...
现在让我们把这个模块附加到我们的应用程序模块上……
app/js/app.js
.
// ...angular.module('phonecatApp', [ 'ngRoute', 'phonecatAnimations', 'phonecatControllers', 'phonecatFilters', 'phonecatServices', ]);// ...
现在,手机分类模块已经有动感了。让我们制作更多更多动画吧!
用CSS过渡动画让ngRepeat动起来。
我们将从这一步开始,把CSS过渡动画添加到出现在phone-list.html
网页上的ngRepeat
指令。首先让我们把一个额外的CSS类添加到我们的重复元素上,因此我们可以把它与我们的CSS动画代码连接。
app/partials/phone-list.html
.
<!-- 让我们改变重复器HTML,以包含一个新的CSS类,之后我们将用它实现动画: --><ul class="phones"> <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail phone-listing"> <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a> <a href="#/phones/{{phone.id}}">{{phone.name}}</a> <p>{{phone.snippet}}</p> </li></ul>
注意我们将如何添加phone-listing
CSS类?这是我们让动画起作用,在HTML代码中所需要做的。
以下是实际的CSS过渡动画代码:
app/css/animations.css
.phone-listing.ng-enter,.phone-listing.ng-leave,.phone-listing.ng-move { -webkit-transition: 0.5s linear all; -moz-transition: 0.5s linear all; -o-transition: 0.5s linear all; transition: 0.5s linear all; }.phone-listing.ng-enter,.phone-listing.ng-move { opacity: 0; height: 0; overflow: hidden; }.phone-listing.ng-move.ng-move-active,.phone-listing.ng-enter.ng-enter-active { opacity: 1; height: 120px; }.phone-listing.ng-leave { opacity: 1; overflow: hidden; }.phone-listing.ng-leave.ng-leave-active { opacity: 0; height: 0; padding-top: 0; padding-bottom: 0; }
如你所见,我们的phone-listing
CSS类与动画钩子相结合,当列表中插入项目或移除项目时,就会出现动画钩子。
当列表中添加了一款新手机并呈现在网页上时,元素上应用了
ng-enter
类。当项目绕着列表移动时,元素上应用了
ng-move
类。当项目从列表中移除时,元素上应用了
ng-leave
类。
添加或删除手机列表项目取决于传递给元素属性ng-repeat
的数据。比如,如果过滤器数据改变了,项目动画地加入或退出重复列表。
有些很重要的事情需要注意,当动画发生时,元素上添加了CSS类的两个集合:
“开始”类代表动画开始时的样式。
“激活”类代表动画结束时的样式。
开始类的名称是被激发的事件(就像enter
、move
或leave
)的名称带上前缀ng-
。所以一个enter
事件将导致一个称为ng-enter
类。
激活类名与开始类名相同,但是带了一个后缀-active
。这两类CSS命名公约允许开发员精心制作一个动画,自始至终。
在我们上面的示例中,当元素添加到列表中时,该元素从0高度伸展到120像素高度;在从列表中移除之前,又收缩到0像素。同时还发生了一个渐现和渐消的效果。这里都是由CSS过渡动画处理的,CSS过渡动画声明在上面示例代码的顶部。
虽然大多数现代浏览器能很好地支持CSS过渡和CSS动画。但是如果你想让动画与老旧的浏览器后向兼容,请考虑使用基于JavaScript的动画,将在下面详细讲解它。
用CSS关键帧动画让ngView
动起来
接下来,让我们为在ngView
内部、路由之间的过渡添加一个动画。
首先,让我们给HTML添加一个新的CSS类,就像我们在上面的示例中所作的。这一次,不是使用ng-repeat
元素,而是把它添加到包含了ng-view
指令的元素上。为了做到这,我们需要对HTML代码做一些小的改变,从而我们可以对我们的动画,在视图改变之间的动画有更多的控制。
app/index.html
.
<div class="view-container"> <div ng-view class="view-frame"></div></div>
利用这个改变,ng-view
指令被嵌套在一个带有view-container
CSS类的父元素内部。这个类添加了一个position: relative
样式,因此动画过程中,ng-view
的定位相对于这个父元素。
在这里,让我们为过渡动画添加CSS,添加到animations.css
文件上:
app/css/animations.css
.
.view-container { position: relative; }.view-frame.ng-enter, .view-frame.ng-leave { background: white; position: absolute; top: 0; left: 0; right: 0; }.view-frame.ng-enter { -webkit-animation: 0.5s fade-in; -moz-animation: 0.5s fade-in; -o-animation: 0.5s fade-in; animation: 0.5s fade-in; z-index: 100; }.view-frame.ng-leave { -webkit-animation: 0.5s fade-out; -moz-animation: 0.5s fade-out; -o-animation: 0.5s fade-out; animation: 0.5s fade-out; z-index:99; } @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @-moz-keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @-webkit-keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } @-moz-keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } @-webkit-keyframes fade-out { from { opacity: 1; } to { opacity: 0; } }/* 别忘了供应商的前缀! */
没什么惊人的!仅仅是两页之间的一个简单的渐显和渐消效果。这里唯不寻常的东西是,在页面之间实现软切换动画的时候,我们在前一页(具有ng-leave
类的页面)的上方使用绝对定位来定位下一页(通过 ng-enter
来指定)。因此前一页即将被删除时,它是渐消淡出的,与此同时新页渐显现在它上面。
一旦离开动画结束,元素会被移除;一旦进入动画结束 ,元素上的ng-enter
和ng-enter-active
CSS类会被移除,导致它用它的默认CSS代码重新呈现、重新定位(因此没有一旦动画结束就没有绝对定位了)。这动作起来非常流畅,因此页面在路由变化时流动自然,不会有任何跳动感。
应用的CSS类(开始和结束类)与ng-repeat
很相像。每当一个新页面载入到ng-view
指令中时,将创建它自己的一个副本,下载模板并追加内容。这确保了所有的视图都包含在一个单独的HTML元素中,该元素允许简单的动画控制。
要想了解更多关于CSS动画的信息,请参阅Web 平台文档。
用JavaScript让ngClass
动起来
让我们向应用程序添加另一个动画。切换到phone-detail.html
网页,我们看到已经有一个很棒的缩略图交换器。通过点击在网页列列中的缩略图,资料手机图像就变了。但是我们可以如何在改变它的同时添加动画呢?
让我们先考虑一下。基本上,当你在一个资料图上点击时,你正在改变图像的状态,以反映新选中的缩略图。在HTML中指定状态改变的最佳方法是使用样式类。和以前很相像,我们使用的CSS样式类以指定指定一个动画,当CSS类本身变化时动画将发生。
当选中了一个新的手机缩略图时,状态改变了,.active
CSS类添加到匹配的资料图像上,并播放了动画。
让我们开始,在phone-detail.html
网贾上微调HTML代码。注意我们已经改变了显示大图像的方式:
app/partials/phone-detail.html
.
<!-- We're only changing the top of the file --><div class="phone-images"> <img ng-src="{{img}}" class="phone" ng-repeat="img in phone.images" ng-class="{active:mainImageUrl==img}"></div><h1>{{phone.name}}</h1><p>{{phone.description}}</p><ul class="phone-thumbs"> <li ng-repeat="img in phone.images"> <img ng-src="{{img}}" ng-mouseenter="setImage(img)"> </li></ul>
就像缩略图,我们使用迭代器来显示所有的资料图像作为一个列表,然而我们没有变动任何迭代相关的动画。而是,我们在ng-class
指令上保持关注,因为每当active
类变成true,则它将应用到元素上,将呈现为可见。否则,资料图像将隐藏。在我们的案例中,总是有一个元素具有active
类,因此,任何时候总会有一款手机的资料图像在屏幕上可见。
当元素上添加了激活类的时候,先添加了active-add
类和adtive-add-active
类,以指示Angular引发一个动画。当元素上移除了激活类的时候,元素上应用了active-remove
类和active-remove-active
,它们反过来又会触发别的动画。
要想确保手机图像在页面第一次加载时正确地显示,我们还要微调详情页的CSS样式:
app/css/app.css
.phone-images { background-color: white; width: 450px; height: 450px; overflow: hidden; position: relative; float: left; } ...img.phone { float: left; margin-right: 3em; margin-bottom: 2em; background-color: white; padding: 2em; height: 400px; width: 400px; display: none; }img.phone:first-child { display: block; }
你可能认为我们将创建另一个CSS可用的动画。虽然我们可以那么做,但是还是让我们抓住机会学习如何用abnimate
模块方法创建JavaScript可用的动画吧。
app/js/animations.js
.
var phonecatAnimations = angular.module('phonecatAnimations', ['ngAnimate']); phonecatAnimations.animation('.phone', function() { var animateUp = function(element, className, done) { if(className != 'active') { return; } element.css({ position: 'absolute', top: 500, left: 0, display: 'block' }); jQuery(element).animate({ top: 0 }, done); return function(cancel) { if(cancel) { element.stop(); } }; } var animateDown = function(element, className, done) { if(className != 'active') { return; } element.css({ position: 'absolute', left: 0, top: 0 }); jQuery(element).animate({ top: -500 }, done); return function(cancel) { if(cancel) { element.stop(); } }; } return { addClass: animateUp, removeClass: animateDown }; });
注意,我们正在使用jQuery以实现这个动画。jQuery并不要求JavaScript动画与AngularJS协作,但是我们将使用它,因为编写你自己的JavaScript动画库超过了这个教程的范围。想要了解更多关于jQuery.animate
的信息,请参阅jQuery文档。
包含我们注册过的类的元素,无论元素上添加了一个类还是移除了一个类,都会调用addClass
回调函数和removeClass
回调函数;在本案例中,注册过的类是.phone
。当元素上添加了.active
类(通过ng-class
指令),将引发addClass
JavaScript回调函数,该回调函数带有一个参数element
。最后传入的参数是done
回调函数。done
回调函数的目的是,通过调用该函数,当JavaScript动画结束时,可以让Angular知道。
removeClass
回调函数以同样的方式起作用,但是是在一个类从元素上移除时触发它。
在JavaScript回调函数中,你通过操纵DOM创建了该动画。在上面的代码中,这就是element.css()
和element.animate()
所做的事情。回调函数用500px
的偏移定位了下一个元素,把前一个项目和新的项目往上移500px
,使两个项目一起动起来。这导致了一个仿传送带的动画。当animate
函数完成它的工作,它会调用 done
。
注意addClass
和removeClass
两者都返回了一个函数。这是一个可选的函数,当动画被取消时(同一个元素上发生了别的动画)时或者动画完成时,会调用这个函数。向这个函数传递一个布尔参数,该参数让开发者知道动画是否被取消了。当动画完成时,这个函数可以用来做一些扫尾工作。
总结
现在你学会了!我们在相对短的时间里创建了一个web应用。在完结篇中我们将讨论接下来何去何从。