| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- var _ = require('../util')
- var queue = require('./queue')
- var addClass = _.addClass
- var removeClass = _.removeClass
- var transitionEndEvent = _.transitionEndEvent
- var animationEndEvent = _.animationEndEvent
- var transDurationProp = _.transitionProp + 'Duration'
- var animDurationProp = _.animationProp + 'Duration'
- var doc = typeof document === 'undefined' ? null : document
- var TYPE_TRANSITION = 1
- var TYPE_ANIMATION = 2
- /**
- * A Transition object that encapsulates the state and logic
- * of the transition.
- *
- * @param {Element} el
- * @param {String} id
- * @param {Object} hooks
- * @param {Vue} vm
- */
- function Transition (el, id, hooks, vm) {
- this.el = el
- this.enterClass = id + '-enter'
- this.leaveClass = id + '-leave'
- this.hooks = hooks
- this.vm = vm
- // async state
- this.pendingCssEvent =
- this.pendingCssCb =
- this.jsCancel =
- this.pendingJsCb =
- this.op =
- this.cb = null
- this.typeCache = {}
- // bind
- var self = this
- ;['enterNextTick', 'enterDone', 'leaveNextTick', 'leaveDone']
- .forEach(function (m) {
- self[m] = _.bind(self[m], self)
- })
- }
- var p = Transition.prototype
- /**
- * Start an entering transition.
- *
- * @param {Function} op - insert/show the element
- * @param {Function} [cb]
- */
- p.enter = function (op, cb) {
- this.cancelPending()
- this.callHook('beforeEnter')
- this.cb = cb
- addClass(this.el, this.enterClass)
- op()
- this.callHookWithCb('enter')
- queue.push(this.enterNextTick)
- }
- /**
- * The "nextTick" phase of an entering transition, which is
- * to be pushed into a queue and executed after a reflow so
- * that removing the class can trigger a CSS transition.
- */
- p.enterNextTick = function () {
- var type = this.getCssTransitionType(this.enterClass)
- var enterDone = this.enterDone
- if (type === TYPE_TRANSITION) {
- // trigger transition by removing enter class now
- removeClass(this.el, this.enterClass)
- this.setupCssCb(transitionEndEvent, enterDone)
- } else if (type === TYPE_ANIMATION) {
- this.setupCssCb(animationEndEvent, enterDone)
- } else if (!this.pendingJsCb) {
- enterDone()
- }
- }
- /**
- * The "cleanup" phase of an entering transition.
- */
- p.enterDone = function () {
- this.jsCancel = this.pendingJsCb = null
- removeClass(this.el, this.enterClass)
- this.callHook('afterEnter')
- if (this.cb) this.cb()
- }
- /**
- * Start a leaving transition.
- *
- * @param {Function} op - remove/hide the element
- * @param {Function} [cb]
- */
- p.leave = function (op, cb) {
- this.cancelPending()
- this.callHook('beforeLeave')
- this.op = op
- this.cb = cb
- addClass(this.el, this.leaveClass)
- this.callHookWithCb('leave')
- // only need to do leaveNextTick if there's no explicit
- // js callback
- if (!this.pendingJsCb) {
- queue.push(this.leaveNextTick)
- }
- }
- /**
- * The "nextTick" phase of a leaving transition.
- */
- p.leaveNextTick = function () {
- var type = this.getCssTransitionType(this.leaveClass)
- if (type) {
- var event = type === TYPE_TRANSITION
- ? transitionEndEvent
- : animationEndEvent
- this.setupCssCb(event, this.leaveDone)
- } else {
- this.leaveDone()
- }
- }
- /**
- * The "cleanup" phase of a leaving transition.
- */
- p.leaveDone = function () {
- this.op()
- removeClass(this.el, this.leaveClass)
- this.callHook('afterLeave')
- if (this.cb) this.cb()
- }
- /**
- * Cancel any pending callbacks from a previously running
- * but not finished transition.
- */
- p.cancelPending = function () {
- this.op = this.cb = null
- var hasPending = false
- if (this.pendingCssCb) {
- hasPending = true
- _.off(this.el, this.pendingCssEvent, this.pendingCssCb)
- this.pendingCssEvent = this.pendingCssCb = null
- }
- if (this.pendingJsCb) {
- hasPending = true
- this.pendingJsCb.cancel()
- this.pendingJsCb = null
- }
- if (hasPending) {
- removeClass(this.el, this.enterClass)
- removeClass(this.el, this.leaveClass)
- }
- if (this.jsCancel) {
- this.jsCancel.call(null)
- this.jsCancel = null
- }
- }
- /**
- * Call a user-provided synchronous hook function.
- *
- * @param {String} type
- */
- p.callHook = function (type) {
- if (this.hooks && this.hooks[type]) {
- this.hooks[type].call(this.vm, this.el)
- }
- }
- /**
- * Call a user-provided, potentially-async hook function.
- * We check for the length of arguments to see if the hook
- * expects a `done` callback. If true, the transition's end
- * will be determined by when the user calls that callback;
- * otherwise, the end is determined by the CSS transition or
- * animation.
- *
- * @param {String} type
- */
- p.callHookWithCb = function (type) {
- var hook = this.hooks && this.hooks[type]
- if (hook) {
- if (hook.length > 1) {
- this.pendingJsCb = _.cancellable(this[type + 'Done'])
- }
- this.jsCancel = hook.call(this.vm, this.el, this.pendingJsCb)
- }
- }
- /**
- * Get an element's transition type based on the
- * calculated styles.
- *
- * @param {String} className
- * @return {Number}
- */
- p.getCssTransitionType = function (className) {
- // skip CSS transitions if page is not visible -
- // this solves the issue of transitionend events not
- // firing until the page is visible again.
- // pageVisibility API is supported in IE10+, same as
- // CSS transitions.
- /* istanbul ignore if */
- if (!transitionEndEvent || (doc && doc.hidden)) {
- return
- }
- var type = this.typeCache[className]
- if (type) return type
- var inlineStyles = this.el.style
- var computedStyles = window.getComputedStyle(this.el)
- var transDuration =
- inlineStyles[transDurationProp] ||
- computedStyles[transDurationProp]
- if (transDuration && transDuration !== '0s') {
- type = TYPE_TRANSITION
- } else {
- var animDuration =
- inlineStyles[animDurationProp] ||
- computedStyles[animDurationProp]
- if (animDuration && animDuration !== '0s') {
- type = TYPE_ANIMATION
- }
- }
- if (type) {
- this.typeCache[className] = type
- }
- return type
- }
- /**
- * Setup a CSS transitionend/animationend callback.
- *
- * @param {String} event
- * @param {Function} cb
- */
- p.setupCssCb = function (event, cb) {
- this.pendingCssEvent = event
- var self = this
- var el = this.el
- var onEnd = this.pendingCssCb = function (e) {
- if (e.target === el) {
- _.off(el, event, onEnd)
- self.pendingCssEvent = self.pendingCssCb = null
- if (!self.pendingJsCb && cb) {
- cb()
- }
- }
- }
- _.on(el, event, onEnd)
- }
- module.exports = Transition
|