【編者按】本文主要通過生動的實例,介紹為 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” 類。
以下是一個說明文檔的截圖,其中說明了需要創建哪些額外的類,命名約定和每個類的添加時間。
根據以上規則,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/