github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/libs/vue-1.0.24/src/transition/transition.js (about)

     1  import { pushJob } from './queue'
     2  import {
     3    on,
     4    off,
     5    bind,
     6    addClass,
     7    removeClass,
     8    cancellable,
     9    transitionEndEvent,
    10    animationEndEvent,
    11    transitionProp,
    12    animationProp,
    13    warn,
    14    inBrowser
    15  } from '../util/index'
    16  
    17  const TYPE_TRANSITION = 'transition'
    18  const TYPE_ANIMATION = 'animation'
    19  const transDurationProp = transitionProp + 'Duration'
    20  const animDurationProp = animationProp + 'Duration'
    21  
    22  /**
    23   * If a just-entered element is applied the
    24   * leave class while its enter transition hasn't started yet,
    25   * and the transitioned property has the same value for both
    26   * enter/leave, then the leave transition will be skipped and
    27   * the transitionend event never fires. This function ensures
    28   * its callback to be called after a transition has started
    29   * by waiting for double raf.
    30   *
    31   * It falls back to setTimeout on devices that support CSS
    32   * transitions but not raf (e.g. Android 4.2 browser) - since
    33   * these environments are usually slow, we are giving it a
    34   * relatively large timeout.
    35   */
    36  
    37  const raf = inBrowser && window.requestAnimationFrame
    38  const waitForTransitionStart = raf
    39    /* istanbul ignore next */
    40    ? function (fn) { raf(function () { raf(fn) }) }
    41    : function (fn) { setTimeout(fn, 50) }
    42  
    43  /**
    44   * A Transition object that encapsulates the state and logic
    45   * of the transition.
    46   *
    47   * @param {Element} el
    48   * @param {String} id
    49   * @param {Object} hooks
    50   * @param {Vue} vm
    51   */
    52  
    53  export default function Transition (el, id, hooks, vm) {
    54    this.id = id
    55    this.el = el
    56    this.enterClass = (hooks && hooks.enterClass) || id + '-enter'
    57    this.leaveClass = (hooks && hooks.leaveClass) || id + '-leave'
    58    this.hooks = hooks
    59    this.vm = vm
    60    // async state
    61    this.pendingCssEvent =
    62    this.pendingCssCb =
    63    this.cancel =
    64    this.pendingJsCb =
    65    this.op =
    66    this.cb = null
    67    this.justEntered = false
    68    this.entered = this.left = false
    69    this.typeCache = {}
    70    // check css transition type
    71    this.type = hooks && hooks.type
    72    /* istanbul ignore if */
    73    if (process.env.NODE_ENV !== 'production') {
    74      if (
    75        this.type &&
    76        this.type !== TYPE_TRANSITION &&
    77        this.type !== TYPE_ANIMATION
    78      ) {
    79        warn(
    80          'invalid CSS transition type for transition="' +
    81          this.id + '": ' + this.type,
    82          vm
    83        )
    84      }
    85    }
    86    // bind
    87    var self = this
    88    ;['enterNextTick', 'enterDone', 'leaveNextTick', 'leaveDone']
    89      .forEach(function (m) {
    90        self[m] = bind(self[m], self)
    91      })
    92  }
    93  
    94  var p = Transition.prototype
    95  
    96  /**
    97   * Start an entering transition.
    98   *
    99   * 1. enter transition triggered
   100   * 2. call beforeEnter hook
   101   * 3. add enter class
   102   * 4. insert/show element
   103   * 5. call enter hook (with possible explicit js callback)
   104   * 6. reflow
   105   * 7. based on transition type:
   106   *    - transition:
   107   *        remove class now, wait for transitionend,
   108   *        then done if there's no explicit js callback.
   109   *    - animation:
   110   *        wait for animationend, remove class,
   111   *        then done if there's no explicit js callback.
   112   *    - no css transition:
   113   *        done now if there's no explicit js callback.
   114   * 8. wait for either done or js callback, then call
   115   *    afterEnter hook.
   116   *
   117   * @param {Function} op - insert/show the element
   118   * @param {Function} [cb]
   119   */
   120  
   121  p.enter = function (op, cb) {
   122    this.cancelPending()
   123    this.callHook('beforeEnter')
   124    this.cb = cb
   125    addClass(this.el, this.enterClass)
   126    op()
   127    this.entered = false
   128    this.callHookWithCb('enter')
   129    if (this.entered) {
   130      return // user called done synchronously.
   131    }
   132    this.cancel = this.hooks && this.hooks.enterCancelled
   133    pushJob(this.enterNextTick)
   134  }
   135  
   136  /**
   137   * The "nextTick" phase of an entering transition, which is
   138   * to be pushed into a queue and executed after a reflow so
   139   * that removing the class can trigger a CSS transition.
   140   */
   141  
   142  p.enterNextTick = function () {
   143    // prevent transition skipping
   144    this.justEntered = true
   145    waitForTransitionStart(() => {
   146      this.justEntered = false
   147    })
   148    var enterDone = this.enterDone
   149    var type = this.getCssTransitionType(this.enterClass)
   150    if (!this.pendingJsCb) {
   151      if (type === TYPE_TRANSITION) {
   152        // trigger transition by removing enter class now
   153        removeClass(this.el, this.enterClass)
   154        this.setupCssCb(transitionEndEvent, enterDone)
   155      } else if (type === TYPE_ANIMATION) {
   156        this.setupCssCb(animationEndEvent, enterDone)
   157      } else {
   158        enterDone()
   159      }
   160    } else if (type === TYPE_TRANSITION) {
   161      removeClass(this.el, this.enterClass)
   162    }
   163  }
   164  
   165  /**
   166   * The "cleanup" phase of an entering transition.
   167   */
   168  
   169  p.enterDone = function () {
   170    this.entered = true
   171    this.cancel = this.pendingJsCb = null
   172    removeClass(this.el, this.enterClass)
   173    this.callHook('afterEnter')
   174    if (this.cb) this.cb()
   175  }
   176  
   177  /**
   178   * Start a leaving transition.
   179   *
   180   * 1. leave transition triggered.
   181   * 2. call beforeLeave hook
   182   * 3. add leave class (trigger css transition)
   183   * 4. call leave hook (with possible explicit js callback)
   184   * 5. reflow if no explicit js callback is provided
   185   * 6. based on transition type:
   186   *    - transition or animation:
   187   *        wait for end event, remove class, then done if
   188   *        there's no explicit js callback.
   189   *    - no css transition:
   190   *        done if there's no explicit js callback.
   191   * 7. wait for either done or js callback, then call
   192   *    afterLeave hook.
   193   *
   194   * @param {Function} op - remove/hide the element
   195   * @param {Function} [cb]
   196   */
   197  
   198  p.leave = function (op, cb) {
   199    this.cancelPending()
   200    this.callHook('beforeLeave')
   201    this.op = op
   202    this.cb = cb
   203    addClass(this.el, this.leaveClass)
   204    this.left = false
   205    this.callHookWithCb('leave')
   206    if (this.left) {
   207      return // user called done synchronously.
   208    }
   209    this.cancel = this.hooks && this.hooks.leaveCancelled
   210    // only need to handle leaveDone if
   211    // 1. the transition is already done (synchronously called
   212    //    by the user, which causes this.op set to null)
   213    // 2. there's no explicit js callback
   214    if (this.op && !this.pendingJsCb) {
   215      // if a CSS transition leaves immediately after enter,
   216      // the transitionend event never fires. therefore we
   217      // detect such cases and end the leave immediately.
   218      if (this.justEntered) {
   219        this.leaveDone()
   220      } else {
   221        pushJob(this.leaveNextTick)
   222      }
   223    }
   224  }
   225  
   226  /**
   227   * The "nextTick" phase of a leaving transition.
   228   */
   229  
   230  p.leaveNextTick = function () {
   231    var type = this.getCssTransitionType(this.leaveClass)
   232    if (type) {
   233      var event = type === TYPE_TRANSITION
   234        ? transitionEndEvent
   235        : animationEndEvent
   236      this.setupCssCb(event, this.leaveDone)
   237    } else {
   238      this.leaveDone()
   239    }
   240  }
   241  
   242  /**
   243   * The "cleanup" phase of a leaving transition.
   244   */
   245  
   246  p.leaveDone = function () {
   247    this.left = true
   248    this.cancel = this.pendingJsCb = null
   249    this.op()
   250    removeClass(this.el, this.leaveClass)
   251    this.callHook('afterLeave')
   252    if (this.cb) this.cb()
   253    this.op = null
   254  }
   255  
   256  /**
   257   * Cancel any pending callbacks from a previously running
   258   * but not finished transition.
   259   */
   260  
   261  p.cancelPending = function () {
   262    this.op = this.cb = null
   263    var hasPending = false
   264    if (this.pendingCssCb) {
   265      hasPending = true
   266      off(this.el, this.pendingCssEvent, this.pendingCssCb)
   267      this.pendingCssEvent = this.pendingCssCb = null
   268    }
   269    if (this.pendingJsCb) {
   270      hasPending = true
   271      this.pendingJsCb.cancel()
   272      this.pendingJsCb = null
   273    }
   274    if (hasPending) {
   275      removeClass(this.el, this.enterClass)
   276      removeClass(this.el, this.leaveClass)
   277    }
   278    if (this.cancel) {
   279      this.cancel.call(this.vm, this.el)
   280      this.cancel = null
   281    }
   282  }
   283  
   284  /**
   285   * Call a user-provided synchronous hook function.
   286   *
   287   * @param {String} type
   288   */
   289  
   290  p.callHook = function (type) {
   291    if (this.hooks && this.hooks[type]) {
   292      this.hooks[type].call(this.vm, this.el)
   293    }
   294  }
   295  
   296  /**
   297   * Call a user-provided, potentially-async hook function.
   298   * We check for the length of arguments to see if the hook
   299   * expects a `done` callback. If true, the transition's end
   300   * will be determined by when the user calls that callback;
   301   * otherwise, the end is determined by the CSS transition or
   302   * animation.
   303   *
   304   * @param {String} type
   305   */
   306  
   307  p.callHookWithCb = function (type) {
   308    var hook = this.hooks && this.hooks[type]
   309    if (hook) {
   310      if (hook.length > 1) {
   311        this.pendingJsCb = cancellable(this[type + 'Done'])
   312      }
   313      hook.call(this.vm, this.el, this.pendingJsCb)
   314    }
   315  }
   316  
   317  /**
   318   * Get an element's transition type based on the
   319   * calculated styles.
   320   *
   321   * @param {String} className
   322   * @return {Number}
   323   */
   324  
   325  p.getCssTransitionType = function (className) {
   326    /* istanbul ignore if */
   327    if (
   328      !transitionEndEvent ||
   329      // skip CSS transitions if page is not visible -
   330      // this solves the issue of transitionend events not
   331      // firing until the page is visible again.
   332      // pageVisibility API is supported in IE10+, same as
   333      // CSS transitions.
   334      document.hidden ||
   335      // explicit js-only transition
   336      (this.hooks && this.hooks.css === false) ||
   337      // element is hidden
   338      isHidden(this.el)
   339    ) {
   340      return
   341    }
   342    var type = this.type || this.typeCache[className]
   343    if (type) return type
   344    var inlineStyles = this.el.style
   345    var computedStyles = window.getComputedStyle(this.el)
   346    var transDuration =
   347      inlineStyles[transDurationProp] ||
   348      computedStyles[transDurationProp]
   349    if (transDuration && transDuration !== '0s') {
   350      type = TYPE_TRANSITION
   351    } else {
   352      var animDuration =
   353        inlineStyles[animDurationProp] ||
   354        computedStyles[animDurationProp]
   355      if (animDuration && animDuration !== '0s') {
   356        type = TYPE_ANIMATION
   357      }
   358    }
   359    if (type) {
   360      this.typeCache[className] = type
   361    }
   362    return type
   363  }
   364  
   365  /**
   366   * Setup a CSS transitionend/animationend callback.
   367   *
   368   * @param {String} event
   369   * @param {Function} cb
   370   */
   371  
   372  p.setupCssCb = function (event, cb) {
   373    this.pendingCssEvent = event
   374    var self = this
   375    var el = this.el
   376    var onEnd = this.pendingCssCb = function (e) {
   377      if (e.target === el) {
   378        off(el, event, onEnd)
   379        self.pendingCssEvent = self.pendingCssCb = null
   380        if (!self.pendingJsCb && cb) {
   381          cb()
   382        }
   383      }
   384    }
   385    on(el, event, onEnd)
   386  }
   387  
   388  /**
   389   * Check if an element is hidden - in that case we can just
   390   * skip the transition alltogether.
   391   *
   392   * @param {Element} el
   393   * @return {Boolean}
   394   */
   395  
   396  function isHidden (el) {
   397    if (/svg$/.test(el.namespaceURI)) {
   398      // SVG elements do not have offset(Width|Height)
   399      // so we need to check the client rect
   400      var rect = el.getBoundingClientRect()
   401      return !(rect.width || rect.height)
   402    } else {
   403      return !(
   404        el.offsetWidth ||
   405        el.offsetHeight ||
   406        el.getClientRects().length
   407      )
   408    }
   409  }