AngularJS 動(dòng)畫(huà)操作

2018-09-28 17:16 更新

動(dòng)畫(huà)操作

最后一步,我們通過(guò)在之前創(chuàng)建的模板代碼上方附加CSS和JavaScript動(dòng)畫(huà)來(lái)加強(qiáng)phonecat web應(yīng)用。

  • 現(xiàn)在我們使用 ngAnimate模塊來(lái)保證動(dòng)畫(huà)可以貫穿這個(gè)應(yīng)用。
  • 另外我們使用常用的ng指令來(lái)自動(dòng)觸發(fā)使動(dòng)畫(huà)接入的掛鉤。
  • 當(dāng)發(fā)現(xiàn)一個(gè)動(dòng)畫(huà)之后,這個(gè)動(dòng)畫(huà)就會(huì)在標(biāo)準(zhǔn)的DOM操作之間運(yùn)行,該操作在給定的時(shí)間中發(fā)布在元素上(如在ngRepeat上插入或刪除節(jié)點(diǎn)或者在ngClass上添加或刪除類)。

工作區(qū)復(fù)位說(shuō)明:

    git checkout -f step-11

下面列出幾種最重要的變化。你可以在 GitHub 上瀏覽全部差異。

依賴關(guān)系

動(dòng)畫(huà)功能由ngAniamte模塊中的Angular提供,它是從核心的Angular框架中分離出來(lái)的。另外,在這個(gè)項(xiàng)目中我們使用jQuery模塊來(lái)完成其余的JavaScript動(dòng)畫(huà)。我們使用的是Bower來(lái)安裝客戶端依賴關(guān)系。這一步更新bower.json配置文件來(lái)包含新的依賴關(guān)系:

{
  "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庫(kù),而是標(biāo)準(zhǔn)的jQuery庫(kù)。我們可以使用bower來(lái)安裝一個(gè)大范圍的第三方庫(kù)。

我們必須要求bower來(lái)下載和安裝這個(gè)依賴關(guān)系。通過(guò)運(yùn)行下述程序來(lái)實(shí)現(xiàn)這一要求:

npm install

警告:如果你上次運(yùn)行npm install后,一個(gè)新版本的Angular已經(jīng)發(fā)布,那么由于需要安裝的angular.js版本之間的沖突,你的bower install可能會(huì)遇到問(wèn)題。如果你有這個(gè)問(wèn)題,那么只需要在運(yùn)行npm install之前,簡(jiǎn)單的刪除你的app/bower_components文件夾。

注意:如果你在全局范圍內(nèi)安裝bower,那么就可以運(yùn)行 bower instal,但是對(duì)于這個(gè)項(xiàng)目來(lái)說(shuō),我們已經(jīng)預(yù)配置了npm install來(lái)運(yùn)行bower。

在 ngAnimate 下動(dòng)畫(huà)是如何工作的

想要了解在AngularJS下動(dòng)畫(huà)是如何工作的,請(qǐng)先閱讀 AngularJS Animation Guide。

模板

我們需要的帶有HTML動(dòng)畫(huà)代碼的更改是用來(lái)鏈接asset文件夾的,這些文件夾定義了動(dòng)畫(huà)和angular-animate.js文件。動(dòng)畫(huà)模塊,即ngAnimate,由angualr-animate.js文件定義,包含了使您的應(yīng)用程序成為動(dòng)畫(huà)意識(shí)的必要的代碼。以下是在索引文件中需要更改的:

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/angularjstutorial/jquery.js"></script>

  ...

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

  <!-- for JavaScript Animations -->
  <script src="/attachments/image/wk/angularjstutorial/animations.js"></script>

...

重要:當(dāng)使用Angular 1.3時(shí),請(qǐng)確保使用jQuery版本2.1或更新的版本;jQuery 1.x官網(wǎng)不在支持。要確保加載 jQuery在所有AngularJS 腳本之前,否則AngularJS不會(huì)檢測(cè)jQuery,并且動(dòng)畫(huà)也不會(huì)像預(yù)期的那樣起作用?,F(xiàn)在可以在CSS代碼(animations.css)和JavaScript代碼(animations.js)中創(chuàng)建動(dòng)畫(huà)。但是在開(kāi)始之前,讓我們創(chuàng)建一個(gè)新的模塊,該模塊使用ngAnimate模塊作為依賴,這就像我們之前使用ngResource一樣。

模塊與動(dòng)畫(huà)

app/js/animations.js.

angular.module('phonecatAnimations', ['ngAnimate']);
// ...
// this module will later be used to define animations
// ...

現(xiàn)在讓我們將這個(gè)模塊附加到應(yīng)用模塊中…

app/js/app.js.

// ...
angular.module('phonecatApp', [
  'ngRoute',

  'phonecatAnimations',
  'phonecatControllers',
  'phonecatFilters',
  'phonecatServices',
]);
// ...

現(xiàn)在,phonecat模塊是動(dòng)畫(huà)意識(shí)的。讓我們做一些動(dòng)畫(huà)吧!

用 CSS 過(guò)渡動(dòng)畫(huà)實(shí)現(xiàn)動(dòng)畫(huà)的 ngRepeat

我們通過(guò)把CSS過(guò)渡動(dòng)畫(huà)添加到ngRepeat指令呈現(xiàn)在phone-list.html頁(yè)面上開(kāi)始。首先,在重復(fù)的元素中添加一個(gè)額外的CSS類,這樣我們可以用CSS動(dòng)畫(huà)代碼來(lái)與它連接。

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代碼中使動(dòng)畫(huà)起作用的全部東西。

以下是實(shí)際的CSS 過(guò)渡動(dòng)畫(huà)代碼: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;
}

正如你所見(jiàn)到的,phone-listing CSS類與動(dòng)畫(huà)鉤相結(jié)合,當(dāng)列表中有項(xiàng)目被插入或者移出的時(shí)候,動(dòng)畫(huà)鉤就會(huì)出現(xiàn)。

  • ng-enter類應(yīng)用于元素中,當(dāng)一個(gè)新的phone添加到列表中并呈現(xiàn)在頁(yè)面上。
  • ng-move類應(yīng)用于列表中項(xiàng)目的移動(dòng)。
  • ng-leave類應(yīng)用于列表中項(xiàng)目刪除。

phone listing項(xiàng)目的添加和刪除取決于傳給ng-repeat屬性的數(shù)據(jù)。例如,若過(guò)濾數(shù)據(jù)改變了,項(xiàng)目就會(huì)動(dòng)畫(huà)地加入到repeat列表中或從repeat列表中動(dòng)畫(huà)地去除。一些重要的聲明,當(dāng)動(dòng)畫(huà)出現(xiàn)時(shí),兩組CSS類會(huì)被添加到元素中:

  1. “starting”類在動(dòng)畫(huà)開(kāi)始時(shí)表明風(fēng)格

  2. “active”類在動(dòng)畫(huà)結(jié)束時(shí)表明風(fēng)格

starting類的名稱是被激發(fā)事件的名稱(如entermoveleave),帶有ng -前綴。所以一個(gè)enter事件將產(chǎn)生一個(gè)稱為ng-enter的類。active類名與starting類名相似,但帶有一個(gè)-active的后綴。這兩類CSS命名約定允許開(kāi)發(fā)人員制作動(dòng)畫(huà),自始至終。

在上面的例子中,當(dāng)添加或刪除項(xiàng)目時(shí),元素從0120像素的高度擴(kuò)展,在從列表中刪除項(xiàng)目之前,元素使項(xiàng)目崩潰。還有一個(gè)漂亮的淡入和淡出效果也在同時(shí)發(fā)生。所有的這些都是由上述示例代碼頂部的CSS過(guò)渡聲明操作的。

盡管大多數(shù)現(xiàn)代的瀏覽器為CSS過(guò)渡和CSS動(dòng)畫(huà)CSS動(dòng)畫(huà)提供了很好的支持,但是IE9和更早的瀏覽器卻沒(méi)有。如果你想使動(dòng)畫(huà)與更早的瀏覽器兼容,考慮使用基于JavaScript的動(dòng)畫(huà),下面會(huì)具體介紹。

用 CSS 關(guān)鍵幀動(dòng)畫(huà)實(shí)現(xiàn)動(dòng)畫(huà)的 ngView

接下來(lái),讓我們?cè)?code>ngView中的路徑更改之間為過(guò)渡添加動(dòng)畫(huà)。

首先,像上述例子一樣,在HTML中添加一個(gè)新的CSS類。這一次,不是ng-repeat元素,而是添加包含ng-view指令的元素。為了實(shí)現(xiàn)這個(gè),我們必須在HTML代碼中做一些小小的改變,以使得在view改變中我們對(duì)動(dòng)畫(huà)有更多的控制。

app/index.html.

 <div class="view-container">
   <div ng-view class="view-frame"></div>
 </div>

通過(guò)這個(gè)改變,ng-view指令是嵌套在一個(gè)帶有view-container CSS類的父元素中。這個(gè)類添加了一個(gè)position:relative樣式,所以定位ng-view是相對(duì)于父元素的,因?yàn)樗M轉(zhuǎn)換。

這里,讓我們?yōu)檫@動(dòng)畫(huà)過(guò)渡將這個(gè)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! */

這里沒(méi)有什么出乎意料的!只是一個(gè)簡(jiǎn)單的頁(yè)面之間的淡入和淡出效果。這里的唯一不尋常的是,在頁(yè)面之間實(shí)現(xiàn)軟切換動(dòng)畫(huà)時(shí),我們?cè)谇耙豁?yè)(有ng-leave類的頁(yè)面)的上方使用絕對(duì)定位來(lái)定位下一個(gè)頁(yè)面(通過(guò)ng-enter識(shí)別)。這樣前一頁(yè)即將被刪除時(shí),它逐漸消失而新的頁(yè)面逐漸出現(xiàn)在它上面。

一旦離開(kāi)動(dòng)畫(huà)結(jié)束元素被移除,一旦進(jìn)入動(dòng)畫(huà)完成時(shí),ng-enterng-enter-active CSS類從元素中被移除,使其rerender和重新定位其默認(rèn)CSS代碼(所以一旦動(dòng)畫(huà)結(jié)束,沒(méi)有絕對(duì)定位)。這運(yùn)作起來(lái)非常流暢,使得頁(yè)面在路徑變化中自然流動(dòng),不會(huì)有任何東西跳動(dòng)。

應(yīng)用的CSS類(開(kāi)始和結(jié)束類)與ng-repeat大體相同。每次加載一個(gè)新頁(yè)面,ng-view指令將創(chuàng)建自身的一個(gè)副本,下載模板并且附加內(nèi)容。這將確保所有的視圖都包含在一個(gè)單獨(dú)的HTML元素中,該元素允許簡(jiǎn)單的動(dòng)畫(huà)控制。

更多關(guān)于 CSS 動(dòng)畫(huà),請(qǐng)看 Web Platform documentation。

用JavaScript 實(shí)現(xiàn)動(dòng)畫(huà)的 ngClass

讓我們?cè)趹?yīng)用程序中添加另一個(gè)動(dòng)畫(huà)。切換到phone-detail.html頁(yè)面,我們看到,我們有一個(gè)不錯(cuò)的縮略圖交換程序。通過(guò)點(diǎn)擊頁(yè)面上列出的縮略圖,這個(gè)手機(jī)圖片改變了。但我們?cè)鯓硬拍芨淖冞@些添加動(dòng)畫(huà)?

讓我們先考慮一下?;旧?當(dāng)你點(diǎn)擊縮略圖,你正在改變圖像的狀態(tài)來(lái)反映新選中的縮略圖。HTML內(nèi)來(lái)指定狀態(tài)改變的最好辦法是使用類。像之前一樣,我們?nèi)绾问褂肅SS類指定一個(gè)動(dòng)畫(huà),這一次每當(dāng)CSS類自身變化時(shí),動(dòng)畫(huà)就會(huì)出現(xiàn)。

每當(dāng)一個(gè)新的手機(jī)縮略圖被選中時(shí),狀態(tài)改變,.active CSS類添加到匹配的圖像中,動(dòng)畫(huà)出現(xiàn)。

首先讓我們對(duì)phone-detail.html頁(yè)面上的 HTML代碼稍作調(diào)整。請(qǐng)注意,我們已經(jīng)改變了顯示大圖的方式:

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>

就像縮略圖,我們使用一個(gè)中繼器來(lái)顯示所有的圖片,這些圖片作為一個(gè)列表。然而我們不是使任何repeat-related動(dòng)畫(huà)來(lái)產(chǎn)生動(dòng)作。相反,我們關(guān)注ng-class指令因?yàn)槿?code>active類是正確的,那么它將被應(yīng)用到元素中并且是可見(jiàn)的。否則,圖像是隱藏的。在我們的例子中,總有一個(gè)元素有active類,,因此,總會(huì)有一個(gè)手機(jī)圖片在屏幕上可見(jiàn)。

當(dāng)活躍類添加到元素中, 在給AngularJS信號(hào)使它發(fā)射一個(gè)動(dòng)畫(huà)之前,active-add類和active-add-active類被添加。當(dāng)活躍類被刪除時(shí),active-remove類和active-remove-active類應(yīng)用于元素,該元素會(huì)反過(guò)來(lái)觸發(fā)另一個(gè)動(dòng)畫(huà)。

確保手機(jī)圖片在第一次加載頁(yè)面時(shí)正確顯示,我們也調(diào)整細(xì)節(jié)頁(yè)面的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;
  }

你可能會(huì)想,我們只是要?jiǎng)?chuàng)建另一個(gè)CSS-enabled動(dòng)畫(huà)。雖然我們可以那樣做,但是讓我們抓住機(jī)會(huì)來(lái)學(xué)習(xí)如何使用animation()模塊來(lái)創(chuàng)建javascript-enabled動(dòng)畫(huà)的方法。

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 來(lái)實(shí)現(xiàn)動(dòng)畫(huà)。有了AngularJS,jQuery不要求做JavaScript動(dòng)畫(huà),但是我們要用它,因?yàn)榫帉?xiě)自己的JavaScript動(dòng)畫(huà)庫(kù)已經(jīng)超出了本教程的范圍。更多關(guān)于jQuery.animate,看 jQuery documentation。當(dāng)在元素中添加或刪除一個(gè)類時(shí),使用addClassremoveClass回調(diào)函數(shù),該元素包含我們注冊(cè)的類,這是在這種情況下.phone。當(dāng)?;钴S的類添加到元素(通過(guò)ng-class指令)theaddClass JavaScript回調(diào)將發(fā)射元素作為參數(shù)傳遞給回調(diào)。傳入的最后一個(gè)參數(shù)是done回調(diào)函數(shù)。done回調(diào)函數(shù)的作用是,通過(guò)調(diào)用該函數(shù),可以使Angular知道JavaScript動(dòng)畫(huà)結(jié)束。

removeClass回調(diào)以同樣的方式工作,當(dāng)從元素中國(guó)移除一個(gè)類時(shí)得到觸發(fā)。

在JavaScript回調(diào)中,通過(guò)操縱DOM創(chuàng)建動(dòng)畫(huà)。在上面的代碼中,這是element.css()element.animate()所做的。回調(diào)用一個(gè)500像素的偏移量定位下一個(gè)元素的位置,并通過(guò)把每個(gè)項(xiàng)目上移500像素來(lái)定位之前的動(dòng)畫(huà)以及新的項(xiàng)目。這產(chǎn)生一個(gè)像動(dòng)畫(huà)一樣的傳送帶。當(dāng)animate函數(shù)完成后,調(diào)用done。

注意addClassremoveClass每個(gè)返回一個(gè)函數(shù)。這是一個(gè)可選的函數(shù),當(dāng)動(dòng)畫(huà)消失時(shí)(當(dāng)另一個(gè)動(dòng)畫(huà)在相同的元素中替代) 以及動(dòng)畫(huà)完成后,可以被調(diào)用。一個(gè)布爾參數(shù)傳遞到函數(shù)中,可以讓開(kāi)發(fā)人員知道動(dòng)畫(huà)是否被取消了。當(dāng)動(dòng)畫(huà)結(jié)束時(shí),這個(gè)函數(shù)可以用來(lái)做任何必要的清理工作。

總結(jié)

現(xiàn)在你學(xué)會(huì)了!我們已經(jīng)在一個(gè)相對(duì)短的時(shí)間內(nèi)創(chuàng)建了一個(gè)web應(yīng)用程序。在 完結(jié)篇 中,我們將討論這里的東西給我們的指引。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)