var endEvents = sniffEndEvents(), config = require('./config'), // batch enter animations so we only force the layout once Batcher = require('./batcher'), batcher = new Batcher(), // cache timer functions setTO = window.setTimeout, clearTO = window.clearTimeout, // exit codes for testing codes = { CSS_E : 1, CSS_L : 2, JS_E : 3, JS_L : 4, CSS_SKIP : -1, JS_SKIP : -2, JS_SKIP_E : -3, JS_SKIP_L : -4, INIT : -5, SKIP : -6 } // force layout before triggering transitions/animations batcher._preFlush = function () { /* jshint unused: false */ var f = document.body.offsetHeight } /** * stage: * 1 = enter * 2 = leave */ var transition = module.exports = function (el, stage, cb, compiler) { var changeState = function () { cb() compiler.execHook(stage > 0 ? 'attached' : 'detached') } if (compiler.init) { changeState() return codes.INIT } var hasTransition = el.vue_trans === '', hasAnimation = el.vue_anim === '', effectId = el.vue_effect if (effectId) { return applyTransitionFunctions( el, stage, changeState, effectId, compiler ) } else if (hasTransition || hasAnimation) { return applyTransitionClass( el, stage, changeState, hasAnimation ) } else { changeState() return codes.SKIP } } /** * Togggle a CSS class to trigger transition */ function applyTransitionClass (el, stage, changeState, hasAnimation) { if (!endEvents.trans) { changeState() return codes.CSS_SKIP } // if the browser supports transition, // it must have classList... var onEnd, classList = el.classList, existingCallback = el.vue_trans_cb, enterClass = config.enterClass, leaveClass = config.leaveClass, endEvent = hasAnimation ? endEvents.anim : endEvents.trans // cancel unfinished callbacks and jobs if (existingCallback) { el.removeEventListener(endEvent, existingCallback) classList.remove(enterClass) classList.remove(leaveClass) el.vue_trans_cb = null } if (stage > 0) { // enter // set to enter state before appending classList.add(enterClass) // append changeState() // trigger transition if (!hasAnimation) { batcher.push({ execute: function () { classList.remove(enterClass) } }) } else { onEnd = function (e) { if (e.target === el) { el.removeEventListener(endEvent, onEnd) el.vue_trans_cb = null classList.remove(enterClass) } } el.addEventListener(endEvent, onEnd) el.vue_trans_cb = onEnd } return codes.CSS_E } else { // leave if (el.offsetWidth || el.offsetHeight) { // trigger hide transition classList.add(leaveClass) onEnd = function (e) { if (e.target === el) { el.removeEventListener(endEvent, onEnd) el.vue_trans_cb = null // actually remove node here changeState() classList.remove(leaveClass) } } // attach transition end listener el.addEventListener(endEvent, onEnd) el.vue_trans_cb = onEnd } else { // directly remove invisible elements changeState() } return codes.CSS_L } } function applyTransitionFunctions (el, stage, changeState, effectId, compiler) { var funcs = compiler.getOption('effects', effectId) if (!funcs) { changeState() return codes.JS_SKIP } var enter = funcs.enter, leave = funcs.leave, timeouts = el.vue_timeouts // clear previous timeouts if (timeouts) { var i = timeouts.length while (i--) { clearTO(timeouts[i]) } } timeouts = el.vue_timeouts = [] function timeout (cb, delay) { var id = setTO(function () { cb() timeouts.splice(timeouts.indexOf(id), 1) if (!timeouts.length) { el.vue_timeouts = null } }, delay) timeouts.push(id) } if (stage > 0) { // enter if (typeof enter !== 'function') { changeState() return codes.JS_SKIP_E } enter(el, changeState, timeout) return codes.JS_E } else { // leave if (typeof leave !== 'function') { changeState() return codes.JS_SKIP_L } leave(el, changeState, timeout) return codes.JS_L } } /** * Sniff proper transition end event name */ function sniffEndEvents () { var el = document.createElement('vue'), defaultEvent = 'transitionend', events = { 'webkitTransition' : 'webkitTransitionEnd', 'transition' : defaultEvent, 'mozTransition' : defaultEvent }, ret = {} for (var name in events) { if (el.style[name] !== undefined) { ret.trans = events[name] break } } ret.anim = el.style.animation === '' ? 'animationend' : 'webkitAnimationEnd' return ret } // Expose some stuff for testing purposes transition.codes = codes transition.sniff = sniffEndEvents