加入“動效”是讓用戶對應用的行為進行感知的一種有效手段。“列表”是應用中最常使用的一種界面形式,經常會有添加行,刪除行,移動行這些操作。設想添加的操作很簡單,刪除時從大到小,然后消失;添加時從小到大;移動就是先刪除再添加。感覺上并不復雜,應該利用CSS的transition就能搞定,可是實際做起來發現有不少問題要處理,下面一一道來。
來些簡單的測試
1、最初的版本
<div class='list'> <div class='row-1'>row-1</div> <div class='row-2'>row-2</div></div>
.list{margin:20px;background:#eee;font-size:18px;color:white;}.row-1{background:green;overflow:hidden;padding:15px;}.row-2{background:blue;padding:15px;}/*demo1*/.demo-1 .remove{-webkit-transition: height 3s linear;}.demo-1 .remove.active{height:0;}var ele = document.querySelector('.demo-1 .row-1');ele.classList.add('remove');ele.classList.add('active');想法很簡單,通過添加“remove”類,設置動畫的效果,添加“active”修改css屬性,激活動畫。

結果和想的不一樣,兩個問題:1、動畫并沒有運行;2、row-1并沒有消失。為什么?首先,CSS的transition不能作用于auto的屬性,因為row-1本來并沒有設置height,所以不會產生從現有的高度變到0的動畫。第二,height=0只是設置了content區域為0,padding并沒有改變,所以還是row-1還是占據了30px的空間。
2、指定固定的height并且padding也加上動畫
調整CSS
/*demo2*/.demo-2 .row-1{height:48px;}.demo-2 .remove{-webkit-transition: height 3s linear, padding-top 3s linear;}.demo-2 .row-1.remove.active{height:0;padding-top:0;padding-bottom:0;}
這次的效果是對的,row-1從48px邊到0,同時padding也跟著變。
3、還有沒有別的辦法呢?一定要指定height嗎?transform行不行
修改CSS
/*demo3*/.demo-3 .remove{-webkit-transition: -webkit-transform 3s linear,padding 0s linear 3s;}.demo-3 .row-1.remove.active{-webkit-transform-origin:0 0;-webkit-transform:scaleY(0);}
即使沒有設置height,通過transform執行動畫也是沒有問題的。問題是,row-1還在原來的地方,還占著空間,row-2并沒有向上挪。由此帶來個問題,動畫執行完了(包括第2個設置height的例子),row-1并沒有刪除掉,只是看不見了。
4、解決動畫執行完清除元素的問題
修改CSS
修改JS
var ele, l;ele = document.querySelector('.demo-4 .row-1');l = ele.addEventListener('webkitTransitionEnd', function(evt){  if (evt.propertyName === 'height') {    ele.style.display = 'none';       ele.style.height = '';    ele.removeEventListener('webkitTransitionEnd', l, false);  }}, false);ele.style.height = ele.offsetHeight + 'px';ele.classList.add('remove');$timeout(function(){  ele.classList.add('active');  ele.style.height = '0px';});
這次的效果不錯。有幾個注意的地方:1、通過注冊transitionEnd事件可以捕獲到動結束;2、可以同時執行多個動效,每個東西結束都會產生transitionEnd事件,通過事件的“propertyName”可以知道是哪個屬性的動效結束了。
5、用velocity.js也試了一下
CSS不用設置
JS代碼
var ele = document.querySelector('.demo-5 .row-1');Velocity(ele, 'slideUp', { duration: 1000 });
看了看執行的過程,也是修改height和padding。但是,velocity用的是requestAnimationFrame函數。我認為如果動效比較簡單,就不用引入其他的庫了,直接寫出來的運行效果差不多。
6、高度搞明白了,變寬度呢?
調整CSS
.demo-6 .row-1{width:100%;}.demo-6 .remove{-webkit-transition: width 3s linear;}.demo-6 .row-1.remove.active{width:0%;}
雖然寬本身可以通過百分比進行設置,但是height不固定的問題還是存在。
7、用上JS解決變width的問題
設置CSS
.demo-7 .row-1{width:100%;height:48px;}.demo-7 .remove{-webkit-transition: width 3s linear, opacity 3s ease;}.demo-7 .row-1.remove.active{width:0%;opacity:0;}
固定了height已有動效正常了。其他的改進可參照前面的例子了。
二、一個完整的例子
完整的例子實在angular中實現的。angular實現首先一個問題就是在什么時機設置動效?因為,angular是雙向綁定的,如果在controller中刪除了一個對象,渲染界面的時候這個對象就沒了,所以必須介入到數據綁定的過程中。angular提供ngAnimatie這個動畫模塊,試了一下它也確實可以完成ngRepeat列表數據更新的動效。但是要額外引入angular-animation.js,雖然不大,還是覺得不是很有必要。另外,我是在一個已經寫好的框架頁面上加動畫,如果需要引入新的module,需要改框架文件,我覺得不好。試了試動態加載animation模塊也沒成功,所以就研究了一下自己怎么控制動效。
angular即使不加載animation模塊,也有一個$animate,它為動效控制留出了接口。
看JS
var fnEnter = $animate.enter,  fnLeave = $animate.leave;$animate.enter = function() {  var defer = $q.defer(),    e = arguments[0],    p = arguments[1],    a = arguments[2],    options = {      addClass: 'ng-enter'    };  fnEnter.call($animate, e, p, a, options).then(function() {    $animate.addClass(e, 'ng-enter-active').then(function(){      var l = e[0].addEventListener('webkitTransitionEnd', function(){        e[0].classList.remove('ng-enter-active');        e[0].classList.remove('ng-enter');        e[0].removeEventListener('webkitTransitionEnd', l, false);        defer.resolve();      }, false);     });  });  return defer.promise;};$animate.leave = function() {  var defer = $q.defer(),    e = arguments[0];  $animate.addClass(e, 'ng-leave').then(function(){    $animate.addClass(e, 'ng-leave-active').then(function(){      var l = e[0].addEventListener('webkitTransitionEnd', function(){        fnLeave.call($animate, e).then(function(){          defer.resolve();        });      }, false);    });  });  return defer.promise;};ng-repeat進行數據更新是會調用$animate服務的enters,leave和move方法,所以,要自己控制動效就要重寫對應的方法。重寫的時候要用$animate添加,直接在dom上設置有問題。(這一段的angular的邏輯比較底層,沒有太看明白,還需要深入研究。)
另外,在移動行的位置時,要通過$timeout將刪除和插入放到兩個digest循環中處理,否則看不出效果。
var index = records.indexOf($scope.selected),  r = records.splice(index, 1);$timeout(function(){  records.splice(index + 1, 0, r[0]);},500);angular的動畫和digest循環關系密切,看了angular-animation.js的代碼沒看明白,還需要深入研究才行。
新聞熱點
疑難解答