Home > Article > Web Front-end > Summary of problems encountered when adding animation effects to angular_AngularJS
Adding "animation" is an effective way to let users perceive the behavior of the application. "List" is the most commonly used interface form in applications. There are often operations such as adding rows, deleting rows, and moving rows. Imagine that the adding operation is very simple. When deleting, it goes from large to small and then disappears; when adding, it goes from small to large; when moving, it means deleting first and then adding. It doesn't feel complicated, and it should be done by using CSS transition. However, in practice, we found that there are many problems to deal with. Let's go through them one by one.
Let’s do some simple tests
1. Initial version
<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');
The idea is very simple. By adding the "remove" class, set the effect of the animation, add "active" to modify the css attribute and activate the animation.
The result is different from what I expected. There are two problems: 1. The animation does not run; 2. row-1 does not disappear. Why? First of all, CSS transition cannot act on the auto attribute, because row-1 does not originally have a height set, so the animation from the existing height to 0 will not be generated. Second, height=0 only sets the content area to 0, and the padding has not changed, so row-1 still occupies 30px of space.
2. Specify a fixed height and add animation to padding
Adjust 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;}
The effect this time is correct, row-1 goes from 48px to 0, and the padding also changes accordingly.
3. Is there any other way? Do I have to specify height? Is transform okay?
Modify 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);}
Even if the height is not set, there is no problem in performing animation through transform. The problem is that row-1 is still in its original place and still takes up space, and row-2 has not moved up. This brings about a problem. After the animation is executed (including the second example of setting height), row-1 is not deleted, but is invisible.
4. Solve the problem of clearing elements after animation execution
Modify CSS
Modify 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'; });
The effect this time is good. There are a few points to note: 1. By registering the transitionEnd event, you can capture the end of the animation; 2. You can execute multiple animations at the same time. A transitionEnd event will be generated at the end of each thing. You can know which property it is through the "propertyName" of the event. The animation is over.
5. I also tried using velocity.js
No need to set CSS
JS code
var ele = document.querySelector('.demo-5 .row-1'); Velocity(ele, 'slideUp', { duration: 1000 });
Looking at the execution process, I also modified the height and padding. However, velocity uses the requestAnimationFrame function. I think if the animation is relatively simple, there is no need to introduce other libraries, and the running effect of writing it directly will be almost the same.
6. Now that the height is clear, what about changing the width?
Adjust CSS
.demo-6 .row-1{width:100%;} .demo-6 .remove{-webkit-transition: width 3s linear;} .demo-6 .row-1.remove.active{width:0%;}
Although the width itself can be set by percentage, the problem of unfixed height still exists.
7. Use JS to solve the problem of changing width
Set 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的代码没看明白,还需要深入研究才行。