讓 Angular 應用動起來!

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

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

不過,好消息是,Angular 附帶了對動畫的大力支持;當然,壞消息是它可能和預期效果有所出入。Angular 并不能制作動畫,但是為用戶的自定義動畫提供了許多組件。

理解 $animate 和 ngAnimate 模塊

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

因此,開發者要怎么做呢?

如果不使用 Angular,怎樣將動畫添加到Web應用中呢?
你需要:

  • 定義動畫開始和結束的風格;
  • 添加或更改某個元素,并將其設置為起始風格;
  • 設置動畫的結束風格;

通常,你會使用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/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容

  • ng-model 指令ng-model 指令 綁定 HTML 元素 到應用程序數據。ng-model 指令也可以:...
    壬萬er閱讀 887評論 0 2
  • 通過jQuery,您可以選取(查詢,query)HTML元素,并對它們執行“操作”(actions)。 jQuer...
    枇杷樹8824閱讀 669評論 0 3
  • AngularJS是什么?AngularJs(后面就簡稱ng了)是一個用于設計動態web應用的結構框架。首先,它是...
    200813閱讀 1,625評論 0 3
  • 有時候,感覺人真的是一個奇妙的生物,上一刻你還在悲傷,下一刻你卻是由于獲得了一些滿足而變得激情昂揚。 生活中的很多...
    Do_yourself閱讀 984評論 0 0
  • 人類在為自己省力方面,真的可以說孜孜不倦。就為了更快更舒適的移動這件事而言,從最早用騎馬來代步,到后來馬車的發明,...
    叁菇涼閱讀 240評論 0 0