Skip to content

Instantly share code, notes, and snippets.

@yongjun21
Last active May 10, 2019 04:48
Show Gist options
  • Save yongjun21/5059fc74e86b94d0da1c5fa4e6d8adf3 to your computer and use it in GitHub Desktop.
Save yongjun21/5059fc74e86b94d0da1c5fa4e6d8adf3 to your computer and use it in GitHub Desktop.
Directive to animate SVG element in Vue (Final Version)
/*
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