Last active
May 10, 2019 04:48
-
-
Save yongjun21/5059fc74e86b94d0da1c5fa4e6d8adf3 to your computer and use it in GitHub Desktop.
Directive to animate SVG element in Vue (Final Version)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This version works better than the previous one because it supports animating computed properties. | |
i.e. attribute can be a function | |
This is useful for attributes like `transform` or non-linear tween | |
*/ | |
import TweenLite from 'gsap/TweenLite' | |
const currentAnimations = {} | |
const _ANIMATE_ = Symbol('animate') | |
const defaultConfig = { | |
group: 'default', | |
duration: 0.66667, | |
order: 0, | |
interpolate: {} | |
} | |
export default { | |
bind (el, binding) { | |
const target = {_t: 0} | |
const vars = binding.arg ? {[binding.arg]: binding.value} : binding.value | |
Object.keys(vars).forEach(prop => { | |
if (prop === 'animation') return | |
el.setAttribute(prop, target[prop] = vars[prop]) | |
}) | |
el.classList.add('vg-animated') | |
el[_ANIMATE_] = function (vars, done, reverse) { | |
vars = Object.assign({}, vars) | |
const options = Object.assign({}, defaultConfig, vars.animation) | |
delete vars.animation | |
if (typeof options.duration === 'function') { | |
options.duration = options.duration(vars, target) | |
} | |
Object.keys(vars).forEach(prop => { | |
if (!(prop in target)) el.setAttribute(prop, target[prop] = vars[prop]) | |
}) | |
const animating = [] | |
const interpolators = {} | |
Object.keys(vars).forEach(prop => { | |
if (vars[prop] !== target[prop]) { | |
animating.push(prop) | |
if (options.interpolate[prop]) { | |
interpolators[prop] = options.interpolate[prop](target[prop], vars[prop]) | |
delete vars[prop] | |
} | |
} else { | |
delete vars[prop] | |
} | |
}) | |
if (animating.length === 0) return | |
TweenLite.set(target, {_t: 0}) // force reset t | |
Object.assign(vars, { | |
_t: 1, | |
onStart () { | |
el.classList.add('vg-animating') | |
}, | |
onComplete () { | |
el.classList.remove('vg-animating') | |
done && done() | |
}, | |
onUpdate () { | |
Object.keys(interpolators).forEach(prop => { | |
target[prop] = interpolators[prop](target._t) | |
}) | |
animating.forEach(prop => { | |
el.setAttribute(prop, target[prop]) | |
}) | |
} | |
}) | |
const tween = TweenLite[reverse ? 'from' : 'to'](target, options.duration, vars) | |
if (options.group in currentAnimations) { | |
currentAnimations[options.group].push([options.order, tween]) | |
} | |
} | |
}, | |
update (el, binding) { | |
if (shouldNotUpdate(binding.value, binding.oldValue, binding.arg)) return | |
const vars = binding.arg ? {[binding.arg]: binding.value} : binding.value | |
el[_ANIMATE_](vars) | |
} | |
} | |
function shouldNotUpdate (value, oldValue, direct) { | |
if (direct) return value === oldValue | |
return Object.keys(value) | |
.every(prop => prop === 'animation' || value[prop] === oldValue[prop]) | |
} | |
export function queueAnimations (...names) { | |
if (names.length === 0) names.push(defaultConfig.group) | |
if (names.length > 0) { | |
names.forEach(name => { | |
currentAnimations[name] = currentAnimations[name] || [] | |
}) | |
} | |
} | |
export function flushAnimations (name = defaultConfig.group) { | |
if (!(name in currentAnimations)) return [] | |
const queued = currentAnimations[name] | |
delete currentAnimations[name] | |
return queued.sort((a, b) => a[0] - b[0]).map(r => r[1]) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment