讓 Angular 應用動起來!

【編者按】本文主要經過生動的實例,介紹爲 Angular 應用添加動畫的原理與過程。文章系國內 ITOM 管理平臺 OneAPM 編譯呈現。html

咱們知道,Angular 應用在更新 DOM 時,會直接將元素轉儲爲視圖而沒有過渡,其默認的用戶體驗並不和諧。前端

不過,好消息是,Angular 附帶了對動畫的大力支持;固然,壞消息是它可能和預期效果有所出入。Angular 並不能製做動畫,可是爲用戶的自定義動畫提供了許多組件。程序員

理解 $animate 和 ngAnimate 模塊

在非 Angular Javascript 應用中更新 DOM 時,程序員會無心識地在動畫中加入自定義成分;可是,在 Angular 應用中,常常會使用內置指令,而不是在DOM上直接更改。angularjs

所以,開發者要怎麼作呢?api

若是不使用 Angular,怎樣將動畫添加到Web應用中呢?
你須要:瀏覽器

  • 定義動畫開始和結束的風格;微信

  • 添加或更改某個元素,並將其設置爲起始風格;app

  • 設置動畫的結束風格;框架

一般,你會使用Javascript或CSS來完成以上步驟。前端性能

當往 Angular 應用添加動畫時,固然也要遵循這個模式,可是卻以 Angular 特有的方式——動畫代碼徹底從指令代碼分離出來。

這是很好的方法

Angular 的內置指令是預先爲動畫設定的。這就意味着,你可使用許多經過 CSS 類或 Javascript 代碼就能調用的動畫「事件」。這些事件與元素或類的添加/刪除相對應。

這可能聽起來有點怪,但其好處是你能夠建立自定義指令,而後讓這些指令的終端用戶自定義他們的動畫。

代碼複用 FTW

這正是 Angular 設計指令的特有方式。這樣一來,因爲 Angular 沒有預約義動畫,開發和設計人員就能夠選擇本身喜歡的方式來建立動畫,好比利用CSS過渡/動畫或JavaScript庫。

構建本身的指令

若是本身寫一個簡單的自定義指令並作成動畫,更有助於理解各個部分如何協同工做;而後再回過頭來,更容易理解內置指令的工做模式。

下面是一個簡單的指令,旨在無動畫支持時隱藏元素:

app.controller("example", function($scope){
    $scope.awesome = false;
});

app.directive("myHide", function(){
    return {
        restrict: 'A',
        link: function(scope, elem, attrs){
            scope.$watch(attrs.myHide, function(value){
                if (value) {
                    elem.addClass("hide");
                } else {
                    elem.removeClass("hide");
                }
            });
        }
    };
});

myHide 指令關注着一個表達式的取值(本例中 ‘awesome’ 的值),當表達式斷定結果爲真時,在元素中添加類;若爲假,則移除類。由於類集顯示設爲 none,因此當表達式爲真時 myHide 元素爲隱藏狀態。

<div class="myHideExample" ng-controller="example">
    <div class="message">
      <p my-hide="awesome">Hide this text if awesome</p>
    </div>
    <button class="button" ng-click="awesome = !awesome">Toggle awesomeness</button>
  </div>

這有動畫效果,但沒有過渡,只是彈出進出。

不借助 $animate 時,爲指令添加動畫

既然 Angular 動畫只是在關鍵事件元素中添加CSS類(或經過觸發Javascript回調函數,咱們稍後將會介紹),再加上Javascript 只能添加或刪除 CSS 類的約束條件,咱們能夠爲指令添加一個簡單的漸淡動畫。由於Javascript 並不瞭解動畫過程,因此若不定義CSS 類,指令雖然能夠執行,但不會產生動畫效果。

$animate的工做原理

myHide 動畫能使元素的不透明度從1淡化到0(當狀態切換時則反之)。在動畫結束時,顯示應該設置爲none。

這就有一個有趣的問題,由於只有動畫結束時才能將顯示設爲none——不然整個動畫運行時,該元素不可見。所以,須要一個CSS類表明過渡/動畫,還須要另外一個CSS類,方便在全部事情完成後將顯示設爲none。

到目前爲止,CSS 該是什麼樣子?

//the final state
.hide {
    display: none;
}
//the animation
.hide-add-start {
    transition: opacity 1s;
    opacity: 0;
}

接着,再在適當的時候,把指令中的幾行 Javascript 語句加入到類中。

/ add this first to start the animation
elem.addClass("hide-add-start");
setTimeout(function() {
    // add the hide class after animation is finished
    elem.addClass("hide");
    // clean up
    elem.removeClass("hide-add-start");
}, 1000);

因此 .hide-add-start 類添加了過渡效果和最終值,過渡完成以後再添加 .hide 類以便將顯示設爲 none。

用於移除和繪製 hide類動畫的 CSS

. hide-remove {
    transition: opacity 1s;
    opacity: 0;
}
.hide-remove-active {
    opacity: 1;
}

在實現移除 .hide 類的動畫效果時,第一步是將不透明度設爲0;不然,該元素會直接彈出,不存在任何動畫效果。

爲了建立不透明度從0到1過渡效果,須要另外一個類來定義結束狀態。所以,須要一個類來定義起始狀態和過渡/動畫,另外一個類來定義結束狀態的動畫。

Javascript 代碼與添加.hide類的過程幾乎同樣,可是如今須要兩個類。

elem.addClass("hide-remove");
elem.removeClass("hide");
  // cause a reflow
elem[0].offsetHeight;
elem.addClass("hide-remove-active");
setTimeout(function(){
    elem.removeClass("hide-remove");
    elem.removeClass("hide-remove-active");
}, 1000);

移除.hide類和添加.hide-remove-active類之間的那一行代碼會引發迴流。若是沒有那行,瀏覽器就不能應用過渡效果,形成元素直接彈出。

如今,終於知道了 $animate 和 ngAnimate 的工做過程

爲指令添加動畫並不像添加和刪除一個類那樣簡單。你須要知道動畫何時開始、何時結束,開始和結束的狀態,知道後須要 JavaScript 協調這一切,這也正是 $animate 的做用內容。

$Animate 服務有添加/刪除類和元素的方法。當在指令中使用這些方法時,針對製做動畫的元素,Angular 會自動添加和刪除類。

它還能在正確的時間添加或刪除類,所以你能夠自定義開始和結束狀態。不只如此,Angular還能從CSS中讀取時間,以便在同一位置定義時間。

重寫指令以利用$animate

$animate 服務有幾種用於添加/刪除/移動元素或添加/刪除類的方法。其理念是使用這些方法而不是直接操做DOM,並用 Angular 觸發 Javascript 動畫,或添加/刪除額外須要的CSS類。

你無需加載 ngAnimate 就能夠注入 $animate 服務,並且在不觸發動畫的狀況下各個部分都能正常工做。這就太好了,由於即便未定義或使用動畫,你也能夠建立正常工做的自定義指令。

若是但願動畫被激活,就必須下載 ng-animate 模塊 Javascript,並把ng-animate 模塊列入你的應用程序,以下所示:

var app = angular.module('animations', ['ngAnimate']);

有了 $animate,myHide 指令的新版本以下所示:

app.directive("myHide", function($animate){
    return {
        restrict: 'A',
        link: function(scope, elem, attrs){
            scope.$watch(attrs.myHide, function(value){
                if (value) {
                    $animate.addClass(elem, "hide");
                } else {
                    $animate.removeClass(elem, "hide");
                }
            });
        }
    };
});

CSS將略有不一樣。除了要添加到元素中的實際的類,addClass 和 removeClass 語句還添加了兩個附加的類:其中一個用於動畫和起始風格,另外一個用於結束風格。這兩個附加類在結束時都會被刪除。

添加CSS類需遵循命名約定。所以,在本例中,你添加的類是 「hide」 ,則 $animate 會在應定義動畫和起始風格的位置再添加一個 「hide-add」 類,同時在任意結束風格的位置添加一個 「hide-add-active」 類。

如下是一個說明文檔的截圖,其中說明了須要建立哪些額外的類,命名約定和每一個類的添加時間。

讓 Angular 應用動起來!

根據以上規則,CSS 可以下所示:

. .hide-add {
    display: block;
    transition: opacity 1s;
    opacity: 1;
}
.hide-add-active {
    opacity: 0;
}
.hide-remove {
    transition: opacity 1s;
    display: block;
    opacity: 0;
}
.hide-remove-active {
    opacity: 1;
}

「hide-add」 類將顯示值設爲 「block」,由於 「hide」 類在同一時間加入,並設置顯示爲 「none」,而這不是咱們想要的。

即便 $animate 指令只能添加一個類,可是它一樣支持 DOM 上用於添加/刪除CSS類的其餘操做方法,所以你能夠在 Angular 應用上實現幾乎全部動畫。

大多數內置指令都使用 $animate 進行DOM操做,這意味着你一樣能夠爲它們實現動畫。若想了解使用 $animate 的內置指令列表,可點擊此處

ngAnimate 和 Javascript 動畫

你也可使用 Javascript 動畫而不是 CSS 動畫/過渡。下面的實例使用了TweenMax 庫,不過你也可使用其餘本身喜歡的庫。

除了添加 CSS 類,$animate 服務也能觸發你在 APP 中定義的任何JavaScript動畫。

app.directive("myHideJs", function($animate){
    return {
        restrict: 'A',
        link: function(scope, elem, attrs){
            scope.$watch(attrs.myHideJs, function(value){
                if (value) {
                    $animate.addClass(elem, "hide-js");
                } else {
                    $animate.removeClass(elem, "hide-js");
                }
            });
        }
    };
});


app.animation('.hide-js-animated', function(){
    return {
        addClass: function(element, className){
            TweenMax.to(element, 1, {
                'opacity': 0
                });
        },
        removeClass: function(element, className){
            TweenMax.to(element, 1, {
                'opacity': 1
            });
        }
    }
});

能夠看到,在該指令使用 $animate 服務和用其進行 CSS 動畫的方式同樣,並沒有區別。

指令下面是動畫,使用簡單、單一的 CSS 類選擇器來命名。使用該動畫的元素必須包括這個類,不然將沒法進行動畫操做。

由動畫調用返回的對象定義了兩個屬性,addClass 和 removeClass。定義這兩個屬性則是由於指令中用到了addClass 和 removeClass。若是元素從指令中移除或添加,則定義爲 ‘leave’ 和 ‘enter’ 屬性。

你能夠在」由JavaScript定義的動畫」部分查看完整的事件列表
下面是使用JavaScript動畫的Angular模板。請注意,最終要做動畫的元素中的類,要與Angular應用所定義的動畫名稱匹配。

<div class="myHideExample" ng-controller="example">
  <div class="message">
    <p my-hide-js="awesome" class="hide-js-animated">Hide this text if awesome</p>
  </div>
  <button class="button" ng-click="awesome = !awesome">Toggle awesomeness</button>
</div>

實現內置指令的動畫

大多數內置指令都使用 $animate,正如 myHide指令。下面爲ngHide代碼:

var ngHideDirective = ['$animate', function($animate) {
  return {
    restrict: 'A',
    multiElement: true,
    link: function(scope, element, attr) {
      scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
        // The comment inside of the ngShowDirective explains why we add and
        // remove a temporary class for the show/hide animation
        $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
          tempClasses: NG_HIDE_IN_PROGRESS_CLASS
        });
      });
    }
  };
}];

是否是很眼熟?這是由於它幾乎和你這段時間一直在看的 myHide 指令徹底同樣。不過也有少量不一樣,主要是ngHide使用三元運算符來代替 if / else,從而肯定調用 addClass仍是removeClass。

再看看其餘內置指令,就會看到對 $animate的調用。每一個指令的說明文檔記錄了能夠在動畫中使用的事件列表。以後,就只是建立CSS動畫仍是JavaScript動畫,以及將全部名稱都與命名約定相匹配的問題。

厭倦了 Angular的「魔力」?

Angular的學習曲線雖然並不簡單,但歸根結底仍是值得咱們學習的。不過, Angular 充滿了奇怪的新概念,並且最終的結果有時看起來簡直難以想象。

全部的框架都堅持己見,Angular 也不例外。問題在於,經過 Angular能夠建立運行簡單的應用程序;可是,在瞭解它以前,你可能會遇到許多難以檢測和調試的問題。這時候,藉助 OneAPM 提供的檢測工具,就能輕鬆解決這些難題。

OneAPM Browser Insight 是一個基於真實用戶的 Web 前端性能監控平臺,能幫助你們定位網站性能瓶頸,實現網站加速效果可視化;支持瀏覽器、微信、App 瀏覽 HTML 和 HTML5 頁面。想閱讀更多技術文章,請訪問 OneAPM 官方技術博客

本文轉自 OneAPM 官方博客

原文連接:http://www.planningforaliens.com/angular/animate-your-angular-application/

相關文章
相關標籤/搜索