在这一步中,我们将会通过在我们先前创建的模板代码中添加CSS和JavaScript动画效果来扩展我们的web应用。
·我们现在使用ngAnimate模块来允许动画效果贯穿整个应用。
·我们也依赖于自带的指令来自动触发动画来进行开发。
·当一个动画效果被发现时,在给定的时间内,它将会和置于元素中的实际DOM操作一同运行(比如:在ngRepeat中插入/删除节点或在ngClass中添加/删除类)。
最大的不同列举如下,您可以点击这里在GitHub上查看全部的不同。
CSS过渡动画:使ngRepeat有生机
我们将会在位于phoneList组件模板中的ngRepeat指令添加CSS过渡动画来开始我们的故事。我们需要在迭代元素中添加一个额外的CSS类,以使其与我们的CSS动画代码挂钩。
app/phone-list/phone-list.template.html
:
...
<ul class="phones">
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp"
class="thumbnail phone-list-item">
<a href="#!/phones/{{phone.id}}" class="thumb">
<img ng-src="{{phone.imageUrl}}" alt="{{phone.name}}" />
</a>
<a href="#!/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>
...
您注意到新添加的phone-list-item CSS类了吗?这是是我们的HTML代码产生动画效果的全部。
现在来看看实际的CSS过渡动画代码:
app/app.animations.css
:
.phone-list-item.ng-enter,
.phone-list-item.ng-leave,
.phone-list-item.ng-move {
transition: 0.5s linear all;
} .phone-list-item.ng-enter,
.phone-list-item.ng-move {
height: 0;
opacity: 0;
overflow: hidden;
} .phone-list-item.ng-enter.ng-enter-active,
.phone-list-item.ng-move.ng-move-active {
height: 120px;
opacity: 1;
} .phone-list-item.ng-leave {
opacity: 1;
overflow: hidden;
} .phone-list-item.ng-leave.ng-leave-active {
height: 0;
opacity: 0;
padding-bottom: 0;
padding-top: 0;
}
正如您所看到的那样,我们的phone-list-item CSS类在列表中的条款插入和删除时与动画效果挂钩:
·ng-enter类在一部新电话在列表中被添加且传递给页面时被处触发。
·ng-move类在一部电话在页面中的相对位置改变时被触发。
·ng-leave类在列表中的一部电话被移除时被触发。
电话列表中条款的添加和删除基于传递给ngRepeat指令的数据。比如,如果过滤器数据改变了,迭代列表中条款会展示进进出出的效果。
值得一提的是,当一个动画效果发绅士,两个CSS类集合会被添加到元素中:
·一个代表动画开始效果的"starting"类。
·一个代表动画结束效果的"active"类。
starting类的名字就是带有ng-前缀的事件(比如enter,move或leave)的名字。所以一个enter事件会导致添加ng-enter类。
active类的名字源于starting类,通过添加一个-active后缀。这两个类的命名惯例使得开发者可以创建制作一个动画,从开始到结束。
在上面的例子中,加入动画效果的元素在它们被加入列表时,高度从0px扩展到120px,在它们从列表中被移除前,高度会被折叠回0px。同时也会产生一个淡入/淡出效果。所有这些是被声明于最顶层的CSS文件所处理的。
CSS关键框架动画:使ngView有生机
接下来,让我们为ngView中路由的过滤添加动画。
同样的,我们需要在HTML模板中添加一个新的CSS类,这次轮到了ng-view元素,为了为我们的动画效果获取更多“生动的能力”,我们将会通过一个容器元素将[ng-view]元素包起来。
app/index.html
:
<div class="view-container">
<div ng-view class="view-frame"></div>
</div>
我们将一个position: relative风格应用于.view-container容器。所以在动画效果的过程中管理.view-frame元素的位置是很简单的。
一旦我们的预备代码准备好了,让我们来看看这个过渡效果的实际CSS风格。
app/app.animations.css
:
... .view-container {
position: relative;
} .view-frame.ng-enter,
.view-frame.ng-leave {
background: white;
left: 0;
position: absolute;
right: 0;
top: 0;
} .view-frame.ng-enter {
animation: 1s fade-in;
z-index: 100;
} .view-frame.ng-leave {
animation: 1s fade-out;
z-index: 99;
} @keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
} @keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
} /* Older browsers might need vendor-prefixes for keyframes and animation! */
没什么大不了了!仅仅是一个页面间的淡入/淡出效果。唯一与平常不同的就是在离开页面的顶部(由ng-leave类识别),我们使用了绝对布局来确定整个页面的位置(由ng-enter类识别)。同时产生了一个平滑过渡效果。所以,随着原先页面的移除,将会产生淡出效果,这时新页面将在上面有淡入效果。
一旦leave动画结束了,元素会从DOM中移除。同样的,一旦enter动画效果完成了,ng-enter和ng-enter-active CSS类也会在元素中被移除,导致其传递和重定位至默认的CSS风格(所以一旦动画结束之手,绝对布局就不存在了)。随着路由的改变,页面间的转换非常自然,而不是跳来跳去。
应用到的CSS类和ngRepeat非常相似。每次一个新页面加载至ngView指令,都会创建一份备份。下载模板并且添加内容。这确保了所有的视图包含在一个单一的HTML元素中,这允许更简单的动画控制。
关于更多CSS动画,请查看这里。
用JavaScript使ngClass有生机
让我们在应用中添加另一个动画效果,在我们的phone-detail.template.html视图中,我们有一个很棒的略图容器。通过点击页面中列出的略图,电话的介绍图片也会改变。但我们怎样加入动画呢?
让我们首先给它一点思想。一般说来,当用户点击一张略图,介绍图片将会转换成新近选中的略图。使用类在HTML中指定状态的改变是最好的方法。和先前很像--当我们使用一个CSS类来驱动动画--这次当CSS类自身改变时动画将会发生。
每次一张电话略图被选中时,状态会改变,且.selected CSS类将会添加到介绍图片,这样就触发了动画效果。
我们将会从调整phone-detail.template.html代码开始,注意到我们改变了我们展示我们大图片的方式:
app/phone-detail/phone-detail.template.html
:
<div class="phone-images">
<img ng-src="{{img}}" class="phone"
ng-class="{selected: img === $ctrl.mainImageUrl}"
ng-repeat="img in $ctrl.phone.images" />
</div> ...
和略图一样,我们使用迭代器将所有的介绍图展示为一个列表,然而我们并没有任何与迭代器过渡相关的的动画。相反,我们将关注每一个元素类,尤其是selected类,因为它的存在与否会决定元素的可见与否。selected类的添加/删除是由ngClass指令所管理的,基于指定的条件 (img === $ctrl.mainImageUrl
).在我们条件下,始终存在一个元素拥有selected类,因此总是有一张电话介绍图片在屏幕中是可见的。
当selected类添加为一个元素时,selected-add和selected-add-active
类被添加至AngularJS来设置一个动画效果。当selected类被从元素中移除时,selected-remove和selected-remove-active类会在元素中被应用,触发另外的动画。
最后,为了确保当页面第一次加载时,电话图片被正确展示,我们也稍微修改了电话细节的CSS风格:
app/app.css
:
... .phone {
background-color: white;
display: none;
float: left;
height: 400px;
margin-bottom: 2em;
margin-right: 3em;
padding: 2em;
width: 400px;
} .phone:first-child {
display: block;
} .phone-images {
background-color: white;
float: left;
height: 450px;
overflow: hidden;
position: relative;
width: 450px;
} ...
您可能会认为我们打算创建另一个基于CSS的动画效果。虽然我们可以这么做,但让我们在这里学习一下如何用基于JavaScript的.animation() 模块方法来创建一个动画吧。
app/app.animations.js
:
angular.
module('phonecatApp').
animation('.phone', function phoneAnimationFactory() {
return {
addClass: animateIn,
removeClass: animateOut
}; function animateIn(element, className, done) {
if (className !== 'selected') return; element.
css({
display: 'block',
position: 'absolute',
top: 500,
left: 0
}).
animate({
top: 0
}, done); return function animateInEnd(wasCanceled) {
if (wasCanceled) element.stop();
};
} function animateOut(element, className, done) {
if (className !== 'selected') return; element.
css({
position: 'absolute',
top: 0,
left: 0
}).
animate({
top: -500
}, done); return function animateOutEnd(wasCanceled) {
if (wasCanceled) element.stop();
};
}
});
我们通过指定一个CSS类选择器(这里是.phone)和一个动画工厂函数(这里是phoneAnimationFactory())来创建一个自定义的动画效果。工厂函数返回一个从时间(对象键)指向动画回调(对象值)的对象。事件相当于ngAnimate识别和能连接的DOM行为,比如addClass
/removeClass
/setClass,
enter
/move
/leave和
animate 。相关的回调会被ngAnimate调用适当的次数。
更多关于动画工厂,请查看API Reference。
在这种情况下,我们感兴趣的是在一个类中.phone元素的添加/删除。因此我们为addClass和removeClass事件指定回调函数。当selected类被添加为一个元素时(经由ngClass指令),addClass JavaScript回调函数将被执行,伴随着element作为一个传递的参数。最后一个传递的参数是done回调函数。我们调用done()来告诉Angular我们自定义的JavaScript已经结束了。removeClass用相同的方式工作,不同的是这在类被移除时执行。
注意到我们使用了jQuery的animate()来提高动画效果。jQuery中JavaScript动画的实现不需要Angular,但无论如何我们在这里使用了,以此来作为一个范例。更多jQuery.animate()的信息请看 jQuery documentation.
随着事件的回调,我们通过操作DOM来创建动画效果。在上面的代码中,这由element.css()和element.animate()来实现。这样做的结果是一个新元素有一个500px的位置移动,并且所有的元素--无论是先前的还是最新的--都有了一个500px的位置移动。结果就产生了一个传送带一样的动画。在animate()函数完成其动画效果之后,它调用done来提醒Angular。
您可能注意到了每一个动画回调都返回一个函数。这是一个可选的函数,将在动画效果结束时被调用,要么被完全执行,要么被取消(比如另一个动画效果发生在相同的元素上)。一个布尔参数(wasCanceled)被传递给这个函数,使得开发者知道其被取消与否。我们使用这个函数来执行任何必要的清除工作。
实验
·反转动画效果,实现动画效果向下传递。
·想要动画效果运行得更快或更慢,可以传递一个duration参数给.animate():
element.css({...}).animate({...}, 1000 /* 1 second */, done);
·使得动画“不对称”。比如,在新元素放大时将原来的元素淡出:
// animateIn()
element.css({
display: 'block',
opacity: 1,
position: 'absolute',
width: 0,
height: 0,
top: 200,
left: 200
}).animate({
width: 400,
height: 400,
top: 0,
left: 0
}, done); // animateOut()
element.animate({
opacity: 0
}, done);
·设计您自己的酷炫动画吧。
总结
我们的应用现在已经易于使用了,多亏了页面和UI状态间平滑的过渡。
您做到了!我们在相对较短的时间内创建了一个web应用。我们会在下一节中给出下一步指导。