给angular加上动画效遇到的问题总结

加入“动效”是让用户对应用的行为进行感知的一种有效手段。“列表”是应用中最常使用的一种界面形式,经常会有添加行,删除行,移动行这些操作。设想添加的操作很简单,删除时从大到小,然后消失;添加时从小到大;移动就是先删除再添加。感觉上并不复杂,应该利用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

代码如下:

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

这次的效果不错。有几个注意的地方: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的代码没看明白,还需要深入研究才行。

(0)

相关推荐

  • AngularJS页面访问时出现页面闪烁问题的解决

    我们知道在应用的页面或者组件需要加载数据时,浏览器和angular渲染页面都需要消耗一定的时间.这里的间隔可能很小,甚至让人感觉不到区别:但也可能很长,这样会导致让我们的用户看到了没有被渲染过的页面. 这种情况被叫做Flash Of Unrendered Content (FOUC)(K)?and is always unwanted.下面我们将要介绍几个不同的方式防止这种情况发生在我们的用户身上. 1.ng-cloak ng-cloak指令是angular的内置指令,它的作用是隐藏所有被它包含

  • angular十大常见问题

    AngularJS可以视为是一种数据优先的框架,在它的三个层面中,数据模型是骨架,视图模型和业务事件是血肉,视图模板和指令是皮毛,这三层合在一起,就形成了一个活生生的Web应用. 1.ng-if的情况下 ,始终将页面中的元素绑定到对象的属性(data.x)而不是直接绑定到基本变量(x)上.因为ng-if 会(隐式地)产生新作用域. 2.ng-repeat迭代数组的时候,如果数组中有相同值,会有什么问题,如何解决?  加 track by $index 可解决.也可以 trace by 任何一个普

  • Angular 页面跳转时传参问题

    首先,你需要已经配置过你的rout,比如: $stateProvider .state('firstPage',{ url:'/Page/firstPage', templateUrl: 'Page/views/firstPage.html', controller: 'firstPageCtrl' //dependencies: ['service/vipSeachService'] }) .state('secPage', { params:{'message':null}, url: '/

  • 总结十个Angular.js由浅入深的面试问题

    一.ng-show/ng-hide 与 ng-if的区别? 我们都知道ng-show/ng-hide实际上是通过display来进行隐藏和显示的.而ng-if实际上控制dom节点的增删除来实现的.因此如果我们是根据不同的条件来进行dom节点的加载的话,那么ng-if的性能好过ng-show. 二.解释下什么是$rootScrope以及和$scope的区别? 通俗的说$rootScrope 页面所有$scope的父亲. 我们来看下如何产生$rootScope和$scope吧. step1:Angu

  • angularjs在ng-repeat中使用ng-model遇到的问题

    在ng-repeat中使用ng-model时会有许多问题,有的人碰到无法获取绑定的数据内容,有的人遇到改动绑定的数据内容时所有循环生成的内容一起改变.上面的问题我在开发时也遇到过,但是解决后我却怎么也还原不了那种情况了,只能先简单介绍一下无法获取的情景该如何解决. 例如: html: <body> <div ng-controller="selectController"> <div ng-repeat="pop in citylist"

  • AngularJs解决跨域问题案例详解(简单方法)

    首先我们做点准备说明,不然你明白我说的是啥意思别人不明白,就算别人明白了那总有人不明白,那你要说了,我的意思是这个说明必须要做了,答案是必须的,为了更好的方便大家理解嘛. 我们以两个主域名或者一个主域名+一个二级域名为例,均可演示跨域问题. 客户端 a.com 服务端 b.com或者s.a.com angularJs版本 V1.2.25 准备工作做得很充分嘛,就差把我们的编辑器是subline暴露出来了,这个一般人我是不告诉他滴. 有人嚷嚷了,这问题老早就有了,你现在提出来有啥意义呢?难不成你还

  • AngularJS上拉加载问题解决方法

    项目中始终存在一个问题:当在搜索栏输入关键词后(见图1),按照既定的业务逻辑应该是服务端接收到请求后,首先返回查询的前7条数据,待客户端出现上拉加载时,继续查找另外7条数据.但实际情形是不确定的,在服务端控制台(见图2)可看到begno一直到了126,也就是相当于客户端往服务端请求了127次,这是一个令人无法忍受的结果. 图1 客户端搜索栏 图2 服务端控制台 可以断定是客户端的业务逻辑出现了问题.返回到客户端,查看源码逻辑,修改如下: 控制器 $scope.medsearchMore = fu

  • 解决angular的$http.post()提交数据时后台接收不到参数值问题的方法

    写此文的背景:在学习使用angular的$http.post()提交数据时,后台接收不到参数值,于是查阅了相关资料,寻找解决办法. 写此文的目的:通过上面提到的文章中的解决之道,结合自己的经验,总结了如下发现. 前端:html,jquery,angular 后端:java,springmvc 一.平常使用的post提交和接收方式 前端使用jquery提交数据. $.ajax({ url:'/carlt/loginForm', method: 'POST', data:{"name":&

  • AngularJS模块管理问题的非常规处理方法

    1.起因 自己一直做winform,有幸从某个大神手里接了一个node.js,express,angularJS等集众多开源框架的一个项目,赶鸭子上架,于是一边学习,一边用自己以往的思中去整理,重构代码: 2.问题 在某几个环境下,需要用BootStrap的模态框,咱是学win的么.. 自然而然的想到将boosStrp的模态框整成一个通用的东西,四处调用 ... 于是用patial引用对话框文件,对话框本身用anglurJS进行了服务器相关的通讯 . 后来发现,当只用一个的时候是好用的,如果多次

  • 解决angular的post请求后SpringMVC后台接收不到参数值问题的方法

    这是我后台SpringMVC控制器接收isform参数的方法,只是简单的打出它的值: @RequestMapping(method = RequestMethod.POST) @ResponseBody public Map<String, Object> save( @RequestParam(value = "isform", required = false) String isform) { System.out.println("isform value

随机推荐