فهرست منبع

robust transition/animation detection

Evan You 10 سال پیش
والد
کامیت
9d5d4671e0

+ 1 - 0
src/runtime/dom-backend/modules/class.js

@@ -11,6 +11,7 @@ function updateClass (oldVnode, vnode) {
     transitionClass = genClass(transitionClass)
     const cls = concat(concat(staticClass, dynamicClass), transitionClass)
     if (cls !== oldVnode.class) {
+      console.log(cls)
       setClass(el, cls)
     }
     vnode.class = cls

+ 10 - 10
src/runtime/dom-backend/modules/style.js

@@ -18,16 +18,6 @@ const normalize = cached(function (prop) {
   }
 })
 
-function toObject (arr) {
-  const res = arr[0] || {}
-  for (var i = 1; i < arr.length; i++) {
-    if (arr[i]) {
-      extend(res, arr[i])
-    }
-  }
-  return res
-}
-
 function updateStyle (oldVnode, vnode) {
   if (!oldVnode.data.style && !vnode.data.style) {
     return
@@ -55,6 +45,16 @@ function updateStyle (oldVnode, vnode) {
   }
 }
 
+function toObject (arr) {
+  const res = arr[0] || {}
+  for (var i = 1; i < arr.length; i++) {
+    if (arr[i]) {
+      extend(res, arr[i])
+    }
+  }
+  return res
+}
+
 export default {
   create: updateStyle,
   update: updateStyle

+ 122 - 45
src/runtime/dom-backend/modules/transition.js

@@ -1,45 +1,75 @@
 import { addClass, removeClass } from '../class-util'
-import {
-  isIE9,
-  inBrowser,
-  transitionProp,
-  transitionEndEvent,
-  animationProp,
-  animationEndEvent
-} from '../../util/index'
+import { isIE9, inBrowser, cached } from '../../util/index'
 
-export default isIE9 ? {} : {
-  create: function applyEnterTransition (_, vnode) {
-    let data = vnode.data.transition
-    const el = vnode.elm
-    if (data != null) {
-      if (typeof data === 'string') {
-        // pure CSS
-        data = cssTransition(data)
-      }
-      // apply enter class
-      const enterClass = data.enterClass
-      if (enterClass) {
-        addTransitionClass(el, enterClass)
-        nextFrame(() => {
-          removeTransitionClass(el, enterClass)
-        })
-      }
-      const enterActiveClass = data.enterActiveClass
-      if (enterActiveClass) {
-        addTransitionClass(el, enterActiveClass)
-        el.addEventListener(transitionEndEvent, () => {
+const TRANSITION = 'transition'
+const ANIMATION = 'animation'
+
+// Transition property/event sniffing
+let transitionProp
+let transitionEndEvent
+let animationProp
+let animationEndEvent
+if (inBrowser && !isIE9) {
+  const isWebkitTrans =
+    window.ontransitionend === undefined &&
+    window.onwebkittransitionend !== undefined
+  const isWebkitAnim =
+    window.onanimationend === undefined &&
+    window.onwebkitanimationend !== undefined
+  transitionProp = isWebkitTrans ? 'WebkitTransition' : 'transition'
+  transitionEndEvent = isWebkitTrans ? 'webkitTransitionEnd' : 'transitionend'
+  animationProp = isWebkitAnim ? 'WebkitAnimation' : 'animation'
+  animationEndEvent = isWebkitAnim ? 'webkitAnimationEnd' : 'animationend'
+}
+
+const raf = (inBrowser && window.requestAnimationFrame) || setTimeout
+function nextFrame (fn) {
+  raf(() => {
+    raf(fn)
+  })
+}
+
+const autoCssTransition = cached(name => {
+  name = name || 'v'
+  return {
+    enterClass: `${name}-enter`,
+    leaveClass: `${name}-leave`,
+    enterActiveClass: `${name}-enter-active`,
+    leaveActiveClass: `${name}-leave-active`
+  }
+})
+
+function beforeEnter (_, vnode) {
+  const el = vnode.elm
+  let data = vnode.data.transition
+  if (data != null) {
+    if (typeof data === 'string') {
+      data = vnode.data.transition = autoCssTransition(data)
+    }
+    // apply enter class
+    const enterClass = data.enterClass
+    if (enterClass) {
+      addTransitionClass(el, enterClass)
+      nextFrame(() => {
+        removeTransitionClass(el, enterClass)
+      })
+    }
+    const enterActiveClass = data.enterActiveClass
+    if (enterActiveClass) {
+      addTransitionClass(el, enterActiveClass)
+      nextFrame(() => {
+        whenTransitionEnds(el, () => {
           removeTransitionClass(el, enterActiveClass)
         })
-      }
+      })
     }
-  },
-
-  remove: function applyLeaveTransition (vnode, rm) {
-
   }
 }
 
+function onLeave (vnode, rm) {
+  rm()
+}
+
 function addTransitionClass (el, cls) {
   (el._transitionClasses || (el._transitionClasses = [])).push(cls)
   addClass(el, cls)
@@ -50,19 +80,66 @@ function removeTransitionClass (el, cls) {
   removeClass(el, cls)
 }
 
-const raf = (inBrowser && window.requestAnimationFrame) || setTimeout
-function nextFrame (fn) {
-  raf(() => {
-    raf(fn)
-  })
+function whenTransitionEnds (el, cb) {
+  const { type, timeout, propCount } = getTransitionInfo(el)
+  if (!type) return cb()
+  const event = type === TRANSITION ? transitionEndEvent : animationEndEvent
+  let ended = 0
+  const end = () => {
+    el.removeEventListener(event, onEnd)
+    cb()
+  }
+  const onEnd = () => {
+    if (++ended >= propCount) {
+      end()
+    }
+  }
+  setTimeout(() => {
+    if (ended < propCount) {
+      end()
+    }
+  }, timeout)
+  el.addEventListener(event, onEnd)
 }
 
-function cssTransition (name) {
-  name = name || 'v'
+function getTransitionInfo (el) {
+  const styles = window.getComputedStyle(el)
+  // 1. determine the maximum duration (timeout)
+  const transitioneDelays = styles[transitionProp + 'Delay'].split(', ')
+  const transitionDurations = styles[transitionProp + 'Duration'].split(', ')
+  const animationDelays = styles[animationProp + 'Delay'].split(', ')
+  const animationDurations = styles[animationProp + 'Duration'].split(', ')
+  const transitionTimeout = getTimeout(transitioneDelays, transitionDurations)
+  const animationTimeout = getTimeout(animationDelays, animationDurations)
+  const timeout = Math.max(transitionTimeout, animationTimeout)
+  const type = timeout > 0
+    ? transitionTimeout > animationTimeout
+      ? TRANSITION
+      : ANIMATION
+    : null
+  const propCount = type
+    ? type === TRANSITION
+      ? transitionDurations.length
+      : animationDurations.length
+    : 0
   return {
-    enterClass: `${name}-enter`,
-    leaveClass: `${name}-leave`,
-    enterActiveClass: `${name}-enter-active`,
-    leaveActiveClass: `${name}-leave-active`
+    type,
+    timeout,
+    propCount
   }
 }
+
+function getTimeout (delays, durations) {
+  return Math.max.apply(null, durations.map((d, i) => {
+    return toMs(d) + toMs(delays[i])
+  }))
+}
+
+function toMs (s) {
+  return Number(s.slice(0, -1)) * 1000
+}
+
+export default !transitionEndEvent ? {} : {
+  create: beforeEnter,
+  remove: onLeave
+}

+ 0 - 34
src/runtime/util/env.js

@@ -18,40 +18,6 @@ export const isAndroid = UA && UA.indexOf('android') > 0
 export const isIos = UA && /(iphone|ipad|ipod|ios)/i.test(UA)
 export const isWechat = UA && UA.indexOf('micromessenger') > 0
 
-let transitionProp
-let transitionEndEvent
-let animationProp
-let animationEndEvent
-
-// Transition property/event sniffing
-if (inBrowser && !isIE9) {
-  const isWebkitTrans =
-    window.ontransitionend === undefined &&
-    window.onwebkittransitionend !== undefined
-  const isWebkitAnim =
-    window.onanimationend === undefined &&
-    window.onwebkitanimationend !== undefined
-  transitionProp = isWebkitTrans
-    ? 'WebkitTransition'
-    : 'transition'
-  transitionEndEvent = isWebkitTrans
-    ? 'webkitTransitionEnd'
-    : 'transitionend'
-  animationProp = isWebkitAnim
-    ? 'WebkitAnimation'
-    : 'animation'
-  animationEndEvent = isWebkitAnim
-    ? 'webkitAnimationEnd'
-    : 'animationend'
-}
-
-export {
-  transitionProp,
-  transitionEndEvent,
-  animationProp,
-  animationEndEvent
-}
-
 /**
  * Defer a task to execute it asynchronously. Ideally this
  * should be executed as a microtask, so we leverage