[Angular Tutorial] 14 -Animations

在这一步中,我们将会通过在我们先前创建的模板代码中添加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-前缀的事件(比如entermoveleave)的名字。所以一个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-enterng-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-addselected-add-active 类被添加至AngularJS来设置一个动画效果。当selected类被从元素中移除时,selected-removeselected-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/setClassenter/move/leave

animate 。相关的回调会被ngAnimate调用适当的次数。

更多关于动画工厂,请查看API Reference。

在这种情况下,我们感兴趣的是在一个类中.phone元素的添加/删除。因此我们为addClassremoveClass事件指定回调函数。当selected类被添加为一个元素时(经由ngClass指令),addClass JavaScript回调函数将被执行,伴随着element作为一个传递的参数。最后一个传递的参数是done回调函数。我们调用done()来告诉Angular我们自定义的JavaScript已经结束了。removeClass用相同的方式工作,不同的是这在类被移除时执行。

注意到我们使用了jQueryanimate()来提高动画效果。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应用。我们会在下一节中给出下一步指导。

 

posted @ 2016-06-03 17:38  Kris_Chan  阅读(298)  评论(0编辑  收藏  举报