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 }