給angular的repeat列表的行操做加上動效遇到的問題總結

加入「動效」是讓用戶對應用的行爲進行感知的一種有效手段。「列表」是應用中最常使用的一種界面形式,常常會有添加行,刪除行,移動行這些操做。設想添加的操做很簡單,刪除時從大到小,而後消失;添加時從小到大;移動就是先刪除再添加。感受上並不複雜,應該利用CSS的transition就能搞定,但是實際作起來發現有很多問題要處理,下面一一道來。css

來些簡單的測試

一、最初的版本

<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屬性,激活動畫。web

clipboard.png

結果和想的不同,兩個問題:一、動畫並無運行;二、row-1並無消失。爲何?首先,CSS的transition不能做用於auto的屬性,由於row-1原本並無設置height,因此不會產生從現有的高度變到0的動畫。第二,height=0只是設置了content區域爲0,padding並無改變,因此仍是row-1仍是佔據了30px的空間。promise

二、指定固定的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;}

clipboard.png

此次的效果是對的,row-1從48px邊到0,同時padding也跟着變。dom

三、還有沒有別的辦法呢?必定要指定height嗎?transform行不行

修改CSSide

/*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);}

clipboard.png
即便沒有設置height,經過transform執行動畫也是沒有問題的。問題是,row-1還在原來的地方,還佔着空間,row-2並無向上挪。由此帶來個問題,動畫執行完了(包括第2個設置height的例子),row-1並無刪除掉,只是看不見了。函數

四、解決動畫執行完清除元素的問題

修改CSS測試

.demo-4 .remove{-webkit-transition: height 3s linear, padding 3s linear, opacity 3s linear,color .5s linear;}
.demo-4 .row-1.remove.active{padding-top:0;padding-bottom:0;color:rgba(0,0,0,0);opacity:0;}

修改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';
});

clipboard.png

此次的效果不錯。有幾個注意的地方:一、經過註冊transitionEnd事件能夠捕獲到動結束;二、能夠同時執行多個動效,每一個東西結束都會產生transitionEnd事件,經過事件的「propertyName」能夠知道是哪一個屬性的動效結束了。spa

五、用velocity.js也試了一下

CSS不用設置
JS代碼

var ele =  document.querySelector('.demo-5 .row-1');
Velocity(ele, 'slideUp', { duration: 1000 });

clipboard.png

看了看執行的過程,也是修改height和padding。可是,velocity用的是requestAnimationFrame函數。我認爲若是動效比較簡單,就不用引入其餘的庫了,直接寫出來的運行效果差很少。

六、高度搞明白了,變寬度呢?

調整CSS

.demo-6 .row-1{width:100%;}
.demo-6 .remove{-webkit-transition: width 3s linear;}
.demo-6 .row-1.remove.active{width:0%;}

clipboard.png

雖然寬自己能夠經過百分比進行設置,可是height不固定的問題仍是存在。

七、用上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;}

clipboard.png

固定了height已有動效正常了。其餘的改進可參照前面的例子了。

2、一個完整的例子

完整的例子實在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的代碼沒看明白,還須要深刻研究才行。

示例

基礎的示例
完整示例

相關文章
相關標籤/搜索