当前位置: 首页 > Web前端 > JavaScript

vue0.11版本源码阅读系列之六:过渡原理

时间:2023-03-27 14:00:43 JavaScript

csstransition先看看这个版本的vuecsstransition和动画的应用:你好!

看着我!

.msg{transition:all.3sease;高度:30px;填充:10px;背景色:#eee;溢出:隐藏;}.msg.expand-enter,.msg.expand-leave{height:0;填充:010px;opacity:0;}.animated{display:inline-block;}.animated.bounce-enter{animation:bounce-in.5s;}.animated.bounce-leave{animation:bounce-out.5s;}@keyframesbounce-in{0%{变换:比例(0);}50%{变换:缩放(1.5);}100%{转换:比例(1);}}@keyframes反弹{0%{transform:scale(1);}50%{变换:缩放(1.5);}100%{转换:比例(0);}}可见也是通过指令。这个版本只支持两个类,一个是进入时添加的v-enter,一个是离开时添加的v-leave。先看看这条命令:module.exports={isLiteral:true,//true不会创建观察者实例bind:function(){this.update(this.expression)},update:function(id){varvm=this.el.__vue__||this.vmthis.el.__v_trans={id:id,//这个版本的vue可以使用transitions选项来定义JavaScript动画fns:vm.$options.transitions[id]}}}this指令不会创建一个watcher,因为该指令的值要么是一个css类名,要么是一个JavaScript动画选项的名称,因此都不需要被监视。该命令在绑定时所做的是给el元素添加一个自定义属性,保存表达式的值,这里是expand,JavaScript动画函数,这里是undefined。要触发动画,需要修改if命令show的值。假设一开始是false,我们改成true,会触发if命令的update方法。根据vue0.11版本源码阅读系列之三:命令编译最后一部分if指令流程分析我们知道进入时会调用transition.blockAppend(frag,this.end,vm),并且transition.blockRemove(this.start,this.end,this.vm)在离开的时候被调用,这里很明显CallblockAppend://block是一个包含if指令绑定元素的代码片段//target是一个注释节点,if指令绑定元素所在的位置exports.blockAppend=function(block,target,vm){//代码块的子节点varnodes=_.toArray(block.childNodes)for(vari=0,l=nodes.length;i0){//enter//添加给元素输入类名addClass(el,enterClass)//op是_.before(nodes[i],target)操作,这一步会向页面添加元素op()push(el,direction,null,enterClass,cb)}else{//leave//给元素添加leave类名addClass(el,leaveClass)push(el,direction,op,leaveClass,cb)}}可以看到进入和离开的操作是不同的。这次我们把show的值改成true,所以会走方向>0的分支,先给元素加上传入的类名,然后才真正把元素插入到页面中,最后调用push方法;如果是离开,先给元素加上离开的类名,然后调用push方法;看一下push方法:varqueue=[]varqueued=falsefunctionpush(el,dir,op,cls,cb){queue.push({el:el,dir:dir,cb:cb,cls:cls,op:op})if(!queued){queued=true_.nextTick(flush)}}添加这个任务去队列里注册一个异步回调,在下一帧执行。nextTick的详细解析请前往vue0.11版本源码阅读系列之五:如何批量更新addClass和op都是同步任务,会立即执行。如果此时这个if指令控制的元素有多个,则依次加入到队列中。结果就是这些元素会被添加到页面中,但是因为我们输入的样式height:0;不透明度:0;已设置,因此它是不可见的。这些同步任务执行完后,会去异步队列中拉出注册的flush方法执行:functionflush(){//这个方法是用来触发强制回流的,保证我们添加的expand-enter样式可以生效,但是我试了下没有reflowvarf=document.documentElement.offsetHeightqueue.forEach(run)queue=[]queued=false}flush方法遍历刚才添加到队列中的任务对象调用了run方法。因为异步批量更新,多个元素动画同时触发只会触发回流:functionrun(job){varel=job.elvardata=el.__v_transvarcls=job.clsvarcb=job.cbvarop=job.op//getTransitionType方法用于获取transition过渡或者animation动画,原理是判断元素的style对象还是获取到的style对象通过getComputedStyle()方法是否存在transitionDuration或animationDuration属性是否为0vartransitionType=getTransitionType(el,data,cls)if(job.dir>0){//enterif(transitionType===1){//transitiontransition//因为v-enter的样式是隐藏元素的样式。另外,因为给元素设置了transition:all.3sease,所以只要删除这个类,自然就会应用transition效果。removeClass(el,cls)//只有当有回调监听tr时才需要ansitionendeventif(cb)setupTransitionCb(_.transitionEndEvent)}elseif(transitionType===2){//animationanimation//animation动画会自己触发只要添加v-enter类,你需要做的是监听animationend事件动画结束后移除这个类)}}else{//Leave//离开动画很简单,两者都可以触发动画,只要加上v-leave类就可以了//你只需要将元素从页面中删除,通过监听动画的结束事件从元素中删除类名if(transitionType){varevent=transitionType===1?_.transitionEndEvent:_.animationEndEventsetupTransitionCb(event,function(){op()removeClass(el,cls)})}else{op()removeClass(el,cls)if(cb)cb()}}}现在看在show的值从true变为false时调用的blockRemove方法处://start和end是两个注释节点,围绕着if指针Exports.blockRemove=function(start,end,vm){varnode=start.nextSiblingvarnextwhile(node!==end){next=node.nextSiblingapply(el,-1,function(){_.remove(el)},vm,cb)node=next}}遍历元素时也会调用apply方法,只是传入参数-1表示离开。这里我们可以总结一下Vue的CSStransition:1.先enter给元素添加v-enter类,然后把元素插入到页面中,最后创建一个task加入到队列中。如果有多个元素,则一次性全部完成,然后在下一帧执行刚刚添加的任务:1.1csstransitionv-enter类名中的样式,一般用于隐藏元素,比如设置元素的宽高为0,透明度为0等等,反正让人看不见就对了。要触发动画,需要删除这个类名,所以这里的任务是删除元素的v-enter类名,然后浏览器会自己应用过渡效果。1.2css动画动画不同。v-enter类的样式一般定义动画的属性值,如:animation:bounce-out.5s;,只要加上这个类名,动画就会开始,所以这里的任务是监听动画结束删除元素的v-enter类名的事件。2.离开时csstransition和animation是一样的。向元素添加一个v-leave类就足够了。v-leave类要设置的样式一般和v-enter一样,除非进入和退出的效果是不一样的,否则需要让元素不可见,然后再添加一个task,因为样式是不可见的,但是元素还在页面上,所以最后的任务就是监听动画结束事件,将元素从页面中移除,当然相应的v-leave类也从元素中移除。JavaScript动画在这个版本中,如果要使用JavaScript进行动画过渡,需要使用声明的过渡选项:Vue.transition('fade',{beforeEnter:function(el){//在元素插入之前调用document,比如提取让元素不可见,否则会有闪屏问题动画完成后手动调用$(el).css('opacity',0).animate({opacity:1},1000,done)//返回动画取消时调用的函数returnfunction(){$(el).stop()}},离开:function(el,done){$(el).animate({opacity:0},1000,done)returnfunction(){$(el).stop()}}})定义了三个钩子函数并定义了JavaScript转换选项。transitioncommand的update方法是可以根据表达式获取,所以会走到上面apply方法中的jsTransition分支,调用applyJSTransition方法:module.exports=function(el,direction,op,data,def,vm,cb){if(data.cancel){data.cancel()data.cancel=null}if(direction>0){//enter//调用beforeEnterhookif(def.beforeEnter){def.beforeEnter.call(vm,el)}op()//将元素插入页面dom//调用enterhookif(def.enter){data.cancel=def.enter.call(vm,el,function(){data.cancel=nullif(cb)cb()})}elseif(cb){cb()}}else{//离开//调用离开钩子if(def.leave){data.cancel=def.leave.call(vm,el,function(){data.cancel=null//离开动画结束,元素从页面中移除op()if(cb)cb()})}else{op()if(cb)cb()}}}相对于css的过渡,JavaScript的过渡非常简单。进入过渡就是在元素真正插入到页面之前执行下面的初始化方法,然后将元素插入到页面中,然后调用enter钩子让元素按照你想要的方式移动。动画结束后,调整vue注入方式,告诉vue动画结束。离开transition的时候,先调整你的leave钩子,在你的动画结束后,从页面中删除该元素,逻辑很简单