silk-gui
Version:
GUI for developers and Node OS
189 lines (174 loc) • 4.71 kB
JavaScript
var _ = require('../util')
var addClass = _.addClass
var removeClass = _.removeClass
var transDurationProp = _.transitionProp + 'Duration'
var animDurationProp = _.animationProp + 'Duration'
var queue = []
var queued = false
/**
* Push a job into the transition queue, which is to be
* executed on next frame.
*
* @param {Element} el - target element
* @param {Number} dir - 1: enter, -1: leave
* @param {Function} op - the actual dom operation
* @param {String} cls - the className to remove when the
* transition is done.
* @param {Function} [cb] - user supplied callback.
*/
function push (el, dir, op, cls, cb) {
queue.push({
el : el,
dir : dir,
cb : cb,
cls : cls,
op : op
})
if (!queued) {
queued = true
_.nextTick(flush)
}
}
/**
* Flush the queue, and do one forced reflow before
* triggering transitions.
*/
function flush () {
/* jshint unused: false */
var f = document.documentElement.offsetHeight
queue.forEach(run)
queue = []
queued = false
}
/**
* Run a transition job.
*
* @param {Object} job
*/
function run (job) {
var el = job.el
var data = el.__v_trans
var cls = job.cls
var cb = job.cb
var op = job.op
var transitionType = getTransitionType(el, data, cls)
if (job.dir > 0) { // ENTER
if (transitionType === 1) {
// trigger transition by removing enter class
removeClass(el, cls)
// only need to listen for transitionend if there's
// a user callback
if (cb) setupTransitionCb(_.transitionEndEvent)
} else if (transitionType === 2) {
// animations are triggered when class is added
// so we just listen for animationend to remove it.
setupTransitionCb(_.animationEndEvent, function () {
removeClass(el, cls)
})
} else {
// no transition applicable
removeClass(el, cls)
if (cb) cb()
}
} else { // LEAVE
if (transitionType) {
// leave transitions/animations are both triggered
// by adding the class, just remove it on end event.
var event = transitionType === 1
? _.transitionEndEvent
: _.animationEndEvent
setupTransitionCb(event, function () {
op()
removeClass(el, cls)
})
} else {
op()
removeClass(el, cls)
if (cb) cb()
}
}
/**
* Set up a transition end callback, store the callback
* on the element's __v_trans data object, so we can
* clean it up if another transition is triggered before
* the callback is fired.
*
* @param {String} event
* @param {Function} [cleanupFn]
*/
function setupTransitionCb (event, cleanupFn) {
data.event = event
var onEnd = data.callback = function transitionCb (e) {
if (e.target === el) {
_.off(el, event, onEnd)
data.event = data.callback = null
if (cleanupFn) cleanupFn()
if (cb) cb()
}
}
_.on(el, event, onEnd)
}
}
/**
* Get an element's transition type based on the
* calculated styles
*
* @param {Element} el
* @param {Object} data
* @param {String} className
* @return {Number}
* 1 - transition
* 2 - animation
*/
function getTransitionType (el, data, className) {
var type = data.cache && data.cache[className]
if (type) return type
var inlineStyles = el.style
var computedStyles = window.getComputedStyle(el)
var transDuration =
inlineStyles[transDurationProp] ||
computedStyles[transDurationProp]
if (transDuration && transDuration !== '0s') {
type = 1
} else {
var animDuration =
inlineStyles[animDurationProp] ||
computedStyles[animDurationProp]
if (animDuration && animDuration !== '0s') {
type = 2
}
}
if (type) {
if (!data.cache) data.cache = {}
data.cache[className] = type
}
return type
}
/**
* Apply CSS transition to an element.
*
* @param {Element} el
* @param {Number} direction - 1: enter, -1: leave
* @param {Function} op - the actual DOM operation
* @param {Object} data - target element's transition data
*/
module.exports = function (el, direction, op, data, cb) {
var prefix = data.id || 'v'
var enterClass = prefix + '-enter'
var leaveClass = prefix + '-leave'
// clean up potential previous unfinished transition
if (data.callback) {
_.off(el, data.event, data.callback)
removeClass(el, enterClass)
removeClass(el, leaveClass)
data.event = data.callback = null
}
if (direction > 0) { // enter
addClass(el, enterClass)
op()
push(el, direction, null, enterClass, cb)
} else { // leave
addClass(el, leaveClass)
push(el, direction, op, leaveClass, cb)
}
}