css过渡
首先看一下这个版本的vue
css
过渡和动画的利用形式:
<p class="msg" v-if="show" v-transition="expand">Hello!</p><p class="animated" v-if="show" v-transition="bounce">Look at me!</p>
.msg { transition: all .3s ease; height: 30px; padding: 10px; background-color: #eee; overflow: hidden;}.msg.expand-enter, .msg.expand-leave { height: 0; padding: 0 10px; opacity: 0;}.animated { display: inline-block;}.animated.bounce-enter { animation: bounce-in .5s;}.animated.bounce-leave { animation: bounce-out .5s;}@keyframes bounce-in { 0% { transform: scale(0); } 50% { transform: scale(1.5); } 100% { transform: scale(1); }}@keyframes bounce-out { 0% { transform: scale(1); } 50% { transform: scale(1.5); } 100% { transform: scale(0); }}
能够看到也是通过指令的形式,这个版本只有反对两个类,一个是进入的时候增加的v-enter
,另一个是来到时候增加的v-leave
。
先看一下这个指令:
module.exports = { isLiteral: true,// 为true不会创立watcher实例 bind: function () { this.update(this.expression) }, update: function (id) { var vm = this.el.__vue__ || this.vm this.el.__v_trans = { id: id, // 这个版本的vue能够应用transitions选项来定义JavaScript动画 fns: vm.$options.transitions[id] } }}
这个指令不会创立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)
,这里显然会调用blockAppend
:
// block是蕴含了if指令绑定元素的代码片段// target是一个正文节点,在if指令绑定元素所在的地位exports.blockAppend = function (block, target, vm) { // 代码片段的子节点 var nodes = _.toArray(block.childNodes) for (var i = 0, l = nodes.length; i < l; i++) { apply(nodes[i], 1, function () { _.before(nodes[i], target) }, vm) }}
遍历元素调用apply
办法:
var apply = exports.apply = function (el, direction, op, vm, cb) { var transData = el.__v_trans if ( !transData ||// 没有过渡数据 !vm._isCompiled ||// 以后实例没有调用过$mount办法插入到页面 (vm.$parent && !vm.$parent._isCompiled)// 父组件没有插入到页面 ) {// 上述情况不须要动画,间接跳过 op() if (cb) cb() return } var jsTransition = transData.fns // JavaScript动画,下一大节再看 if (jsTransition) { applyJSTransition( el, direction, op, transData, jsTransition, vm, cb ) } else if ( _.transitionEndEvent && // 页面不可见的话不进行过渡 !(doc && doc.hidden) ) { // css applyCSSTransition( el, direction, op, transData, cb ) } else { // 不须要利用过渡 op() if (cb) cb() }}
这个办法会判断是利用JavaScript
动画还是css
动画,并分发给不同的函数解决。函数套娃,又套到了applyCSSTransition
办法:
module.exports = function (el, direction, op, data, cb) { var prefix = data.id || 'v'// 此处是expand var enterClass = prefix + '-enter'// expand-enter var leaveClass = prefix + '-leave'// expand-leave if (direction > 0) { // 进入 // 给元素增加进入的类名 addClass(el, enterClass) // op就是_.before(nodes[i], target)操作,这一步会把元素增加到页面上 op() push(el, direction, null, enterClass, cb) } else { // 来到 // 给元素增加来到的类名 addClass(el, leaveClass) push(el, direction, op, leaveClass, cb) }}
能够看到进入和来到的操作是有区别的,本次咱们是把show
的值改成true
,所以会走direction > 0
的分支,先给元素增加进入的类名,而后再把元素理论插入到页面上,最初调用push
办法;
如果是来到的话会先给元素增加来到的类名,而后调用push
办法;
看一下push
办法:
var queue = []var queued = falsefunction push (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;opacity: 0;
,所以是看不见的,这些同步工作执行完后才会去异步队列里把注册的flush
办法拉进去执行:
function flush () { // 这个办法用来触发强制回流,确保咱们增加的expand-enter款式能失效,不过我试过不回流也能失效 var f = document.documentElement.offsetHeight queue.forEach(run) queue = [] queued = false}
flush
办法遍历方才增加到queue
里的工作对象调用run
办法,因为进行了异步批量更新,所以同一时刻有多个元素动画也只会触发一次回流:
function run (job) { var el = job.el var data = el.__v_trans var cls = job.cls var cb = job.cb var op = job.op // getTransitionType办法用来获取是transition过渡还是animation动画,原理是判断元素的style对象或者getComputedStyle()办法获取的款式对象里的transitionDuration或animationDuration属性是否存在以及是否为0s var transitionType = getTransitionType(el, data, cls) if (job.dir > 0) { // 进入 if (transitionType === 1) {// transition过渡 // 因为v-enter的款式是暗藏元素的款式,另外因为给元素设置了transition: all .3s ease,所以只有把这个类删除了天然就会利用过渡成果 removeClass(el, cls) // 存在回调时才须要监听transitionend事件 if (cb) setupTransitionCb(_.transitionEndEvent) } else if (transitionType === 2) {// animation动画 // animation动画只有增加了v-enter类自行就会触发,须要做的只是监听animationend事件在动画完结后把这个类删除 setupTransitionCb(_.animationEndEvent, function () { removeClass(el, cls) }) } else { // 没有过渡 removeClass(el, cls) if (cb) cb() } } else { // 来到 // 来到动画很简略,两者都是只有增加了v-leave类就能够触发动画 // 要做的只是在监听动画完结的事件把元素从页面删除和把类名从元素上删除 if (transitionType) { var event = transitionType === 1 ? _.transitionEndEvent : _.animationEndEvent setupTransitionCb(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) { var node = start.nextSibling var next while (node !== end) { next = node.nextSibling apply(el, -1, function () { _.remove(el) }, vm, cb) node = next }}
遍历元素同样调用apply
办法,只不过参数传了-1
代表是来到。
到这里能够总结一下vue
的css
过渡:
1.进入
先给元素增加v-enter
类,而后把元素插入到页面,最初创立一个工作增加到队列,如果有多个元素的话会一次性全副实现,而后在下一帧来执行方才增加的工作:
1.1css过渡
v-enter
类名里的款式个别是用来暗藏元素的,比方把元素的宽高设为0
、透明度设为0
等等,反正让人看不见就对了,要触发动画须要把这个类名删除了,所以这里的工作就是移除元素的v-enter
类名,而后浏览器会本人利用过渡成果。
1.2css动画
animation
不一样,v-enter
类的款式个别是定义animation
的属性值,比方:animation: bounce-out .5s;
,只有增加了这个类名,就会开始动画,所以这里的工作是监听动画完结事件来移除元素的v-enter
类名。
2.来到
css
过渡和动画在来到时是一样的,都是给元素增加一个v-leave
类就能够了,v-leave
类要设置的款式个别和v-enter
是一样的,除非进出成果就是要不一样,否则都是要让元素不可见,而后增加一个工作,因为款式上不可见了但元素实际上还是在页面上,所以最初的工作就是监听动画完结事件把元素真正的从页面上移除,当然,相应的v-leave
类也是要 从元素上移除的。
JavaScript动画
在这个版本要应用JavaScript
进行动画过渡须要应用申明过渡选项:
Vue.transition('fade', { beforeEnter: function (el) { // 元素插入文档之前调用,比方提取把元素变成不可见,否则会有闪屏的问题 }, enter: function (el, done) { // 元素曾经插入到DOM,动画实现后须要手动调用done办法 $(el) .css('opacity', 0) .animate({ opacity: 1 }, 1000, done) // 返回一个函数当动画勾销时被调用 return function () { $(el).stop() } }, leave: function (el, done) { $(el).animate({ opacity: 0 }, 1000, done) return function () { $(el).stop() } }})
就是定义三个钩子函数,定义了JavaScript
过渡选项,在transition
指令的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) { // 进入 // 调用beforeEnter钩子 if (def.beforeEnter) { def.beforeEnter.call(vm, el) } op()// 把元素插入到页面dom // 调用enter钩子 if (def.enter) { data.cancel = def.enter.call(vm, el, function () { data.cancel = null if (cb) cb() }) } else if (cb) { cb() } } else { // 来到 // 调用leave钩子 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
动画完结了,来到过渡先调一下你的来到钩子,在你的动画完结后再把元素从页面上删除,逻辑很简略。