离线下载
PDF版 ePub版

AngularJS 中文社区 · 更新于 2018-11-28 11:00:42

动画操作 - Applying Animations

最后一步,我们通过在之前创建的模板代码上方附加CSS和JavaScript动画来加强phonecat web应用。

  • 现在我们使用 ngAnimate模块来保证动画可以贯穿这个应用。
  • 另外我们使用常用的ng指令来自动触发使动画接入的挂钩。
  • 当发现一个动画之后,这个动画就会在标准的DOM操作之间运行,该操作在给定的时间中发布在元素上(如在ngRepeat上插入或删除节点或者在ngClass上添加或删除类)。

工作区复位说明:

    git checkout -f step-11

下面列出几种最重要的变化。你可以在 GitHub 上浏览全部差异。

依赖关系

动画功能由ngAniamte模块中的Angular提供,它是从核心的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.3.0",
    "angular-mocks": "~1.3.0",
    "bootstrap": "~3.1.1",
    "angular-route": "~1.3.0",
    "angular-resource": "~1.3.0",
    "jquery": "~2.1.1",
    "angular-animate": "~1.3.0"
  }
}
  • “angular-animate”:“~1.3.0”意味着bower要安装与版本1.3.x兼容的angular-animate组件版本。
  • “jquery”:“2.1.1”意味着bower要安装jQuery2.1.1版本。注意这不是Angular库,而是标准的jQuery库。我们可以使用bower来安装一个大范围的第三方库。

我们必须要求bower来下载和安装这个依赖关系。通过运行下述程序来实现这一要求:

npm install

警告:如果你上次运行npm install后,一个新版本的Angular已经发布,那么由于需要安装的angular.js版本之间的冲突,你的bower install可能会遇到问题。如果你有这个问题,那么只需要在运行npm install之前,简单的删除你的app/bower_components文件夹。

注意:如果你在全局范围内安装bower,那么就可以运行 bower instal,但是对于这个项目来说,我们已经预配置了npm install来运行bower。

在 ngAnimate 下动画是如何工作的

想要了解在AngularJS下动画是如何工作的,请先阅读 AngularJS Animation Guide

模板

我们需要的带有HTML动画代码的更改是用来链接asset文件夹的,这些文件夹定义了动画和angular-animate.js文件。动画模块,即ngAnimate,由angualr-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="bower_components/jquery/dist/jquery.js"></script>

  ...

  <!-- required module to enable animation support in AngularJS -->
  <script src="bower_components/angular-animate/angular-animate.js"></script>

  <!-- for JavaScript Animations -->
  <script src="js/animations.js"></script>

...

重要:当使用Angular 1.3时,请确保使用jQuery版本2.1或更新的版本;jQuery 1.x官网不在支持。要确保加载 jQuery在所有AngularJS 脚本之前,否则AngularJS不会检测jQuery,并且动画也不会像预期的那样起作用。 现在可以在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',
]);
// ...

现在,phonecat模块是动画意识的。让我们做一些动画吧!

用 CSS 过渡动画实现动画的 ngRepeat

我们通过把CSS过渡动画添加到ngRepeat指令呈现在phone-list.html页面上开始。首先,在重复的元素中添加一个额外的CSS类,这样我们可以用CSS动画代码来与它连接。

app/partials/phone-list.html.

    <!--
      Let's change the repeater HTML to include a new CSS class
      which we will later use for animations:
    -->
    <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类应用于元素中,当一个新的phone添加到列表中并呈现在页面上。
  • ng-move类应用于列表中项目的移动。
  • ng-leave类应用于列表中项目删除。

phone listing项目的添加和删除取决于传给ng-repeat属性的数据。例如,若过滤数据改变了,项目就会动画地加入到repeat列表中或从repeat列表中动画地去除。 一些重要的声明,当动画出现时,两组CSS类会被添加到元素中:

  1. “starting”类在动画开始时表明风格

  2. “active”类在动画结束时表明风格

starting类的名称是被激发事件的名称(如entermoveleave),带有ng -前缀。所以一个enter事件将产生一个称为ng-enter的类。 active类名与starting类名相似,但带有一个-active的后缀。这两类CSS命名约定允许开发人员制作动画,自始至终。

在上面的例子中,当添加或删除项目时,元素从0120像素的高度扩展,在从列表中删除项目之前,元素使项目崩溃。还有一个漂亮的淡入和淡出效果也在同时发生。所有的这些都是由上述示例代码顶部的CSS过渡声明操作的。

尽管大多数现代的浏览器为CSS过渡和CSS动画CSS动画提供了很好的支持,但是IE9和更早的浏览器却没有。如果你想使动画与更早的浏览器兼容,考虑使用基于JavaScript的动画,下面会具体介绍。

用 CSS 关键帧动画实现动画的 ngView

接下来,让我们在ngView中的路径更改之间为过渡添加动画。

首先,像上述例子一样,在HTML中添加一个新的CSS类。这一次,不是ng-repeat元素,而是添加包含ng-view指令的元素。为了实现这个,我们必须在HTML代码中做一些小小的改变,以使得在view改变中我们对动画有更多的控制。

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; }
}

/* don't forget about the vendor-prefixes! */

这里没有什么出乎意料的!只是一个简单的页面之间的淡入和淡出效果。这里的唯一不寻常的是,在页面之间实现软切换动画时,我们在前一页(有ng-leave类的页面)的上方使用绝对定位来定位下一个页面(通过ng-enter识别)。这样前一页即将被删除时,它逐渐消失而新的页面逐渐出现在它上面。

一旦离开动画结束元素被移除,一旦进入动画完成时,ng-enterng-enter-active CSS类从元素中被移除,使其rerender和重新定位其默认CSS代码(所以一旦动画结束,没有绝对定位)。这运作起来非常流畅,使得页面在路径变化中自然流动,不会有任何东西跳动。

应用的CSS类(开始和结束类)与ng-repeat大体相同。每次加载一个新页面,ng-view指令将创建自身的一个副本,下载模板并且附加内容。这将确保所有的视图都包含在一个单独的HTML元素中,该元素允许简单的动画控制。

更多关于 CSS 动画,请看 Web Platform documentation

用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>

就像缩略图,我们使用一个中继器来显示所有的图片,这些图片作为一个列表。然而我们不是使任何repeat-related动画来产生动作。相反,我们关注ng-class指令因为若active类是正确的,那么它将被应用到元素中并且是可见的。否则,图像是隐藏的。在我们的例子中,总有一个元素有active类,,因此,总会有一个手机图片在屏幕上可见。

当活跃类添加到元素中, 在给AngularJS信号使它发射一个动画之前,active-add类和active-add-active类被添加。当活跃类被删除时,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-enabled动画。虽然我们可以那样做,但是让我们抓住机会来学习如何使用animation()模块来创建javascript-enabled动画的方法。

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 来实现动画。有了AngularJS,jQuery不要求做JavaScript动画,但是我们要用它,因为编写自己的JavaScript动画库已经超出了本教程的范围。更多关于jQuery.animate,看 jQuery documentation。 当在元素中添加或删除一个类时,使用addClassremoveClass回调函数,该元素包含我们注册的类,这是在这种情况下.phone。当。活跃的类添加到元素(通过ng-class指令)theaddClass JavaScript回调将发射元素作为参数传递给回调。传入的最后一个参数是done回调函数。done回调函数的作用是,通过调用该函数,可以使Angular知道JavaScript动画结束。

removeClass回调以同样的方式工作,当从元素中国移除一个类时得到触发。

在JavaScript回调中,通过操纵DOM创建动画。在上面的代码中,这是element.css()element.animate()所做的。回调用一个500像素的偏移量定位下一个元素的位置,并通过把每个项目上移500像素来定位之前的动画以及新的项目。这产生一个像动画一样的传送带。当animate函数完成后,调用done

注意addClassremoveClass每个返回一个函数。这是一个可选的函数,当动画消失时(当另一个动画在相同的元素中替代) 以及动画完成后,可以被调用。一个布尔参数传递到函数中,可以让开发人员知道动画是否被取消了。当动画结束时,这个函数可以用来做任何必要的清理工作。

总结

现在你学会了!我们已经在一个相对短的时间内创建了一个web应用程序。在 完结篇 中,我们将讨论这里的东西给我们的指引。