'애니메이션'을 추가하는 것은 사용자가 애플리케이션의 동작을 인식할 수 있도록 하는 효과적인 방법입니다. "목록"은 응용 프로그램에서 가장 일반적으로 사용되는 인터페이스 형식으로 행 추가, 행 삭제, 행 이동과 같은 작업이 자주 있습니다. 추가 작업이 매우 간단하다고 상상해 보십시오. 삭제하면 큰 것에서 작은 것으로 이동한 다음 사라지고, 이동할 때 작은 것에서 큰 것으로 이동하는 것은 먼저 삭제한 다음 추가하는 것을 의미합니다. 복잡하게 느껴지지 않으며 CSS 전환을 사용하여 수행해야 하지만 실제로는 해결해야 할 문제가 많다는 사실을 하나씩 살펴보겠습니다.
간단한 테스트를 해보자
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이 사라지지 않습니다. 왜? 우선, row-1에는 원래 높이가 설정되어 있지 않기 때문에 CSS 전환은 auto 속성에 작용할 수 없으므로 기존 높이에서 0으로의 애니메이션은 생성되지 않습니다. 둘째, height=0은 콘텐츠 영역만 0으로 설정하고 패딩은 변경되지 않았으므로 row-1은 여전히 30px의 공간을 차지합니다.
2. 고정 높이를 지정하고 패딩에 애니메이션을 추가합니다
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으로 바뀌고 그에 따라 패딩도 변경됩니다.
3. 다른 방법은 없나요? 높이를 지정해야 하나요? 변신 괜찮나요?
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);}
높이를 설정하지 않더라도 변형을 통한 애니메이션 수행에는 문제가 없습니다. 문제는 행 1이 여전히 원래 위치에 있고 여전히 공간을 차지하고 행 2가 위로 이동되지 않았다는 것입니다. 이로 인해 문제가 발생합니다. 애니메이션이 실행된 후(높이 설정의 두 번째 예 포함) 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 });
실행 과정을 살펴보면서 높이와 패딩도 수정했습니다. 그러나 속도는 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%;}
너비 자체는 백분율로 설정할 수 있지만 높이가 고정되지 않는 문제는 여전히 존재합니다.
7. JS를 사용하여 너비 변경 문제 해결
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的代码没看明白,还需要深入研究才行。