Last active
September 19, 2017 15:14
-
-
Save Jeremboo/6b20e8c69a3005a3c1801e26f6f31467 to your computer and use it in GitHub Desktop.
Class to manage animations with GreenSock
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
/** | |
* V1.0.0 | |
* @author jeremboo https://jeremieboulay.fr | |
* | |
* REACT ANIMATED COMPONENT : | |
* Class to manage react components animated by GreenSock timelines. | |
* You can show/hide child components and their children. | |
* | |
* PARENT: Start to animate components | |
* ------------- | |
* | |
* export default class Parent extends AnimComponent { | |
* constructor(props) { | |
* super(props); | |
* } | |
* | |
* componentDidMount() { | |
* // Start animation of Child1 component then on complete, start Child2 animation | |
* this.updateAnim('child1', true, {}, '+=1') | |
* .then(() => this.updateAnim('child2', true)) | |
* .then(() => { console.log('animationComplete'); }) | |
* ; | |
* } | |
* | |
* render() { | |
* return ( | |
* <section className="Parent"> | |
* <Child anim={this.anims.child1} /> | |
* <Child anim={this.anims.child2} /> | |
* </section> | |
* ); | |
* } | |
* } | |
* | |
* | |
* CHILD: Build the timelines | |
* ----------- | |
* | |
* export default class Child extends AnimComponent { | |
* constructor(props) { | |
* super(props); | |
* this.section = false; | |
* } | |
* | |
* componentDidMount() { | |
* // Timeline lautched to show the component | |
* this.timelineIn.fromTo(this.section, 0.3, { opacity: 0 }, { opacity: 1 }); | |
* // Timeline lautched to hide the component | |
* this.timelineOut.to(this.section, 0.5, { opacity: 0 }); | |
* super.componentDidMount(); | |
* } | |
* | |
* componentDidUpdate() { super.componentDidUpdate(); } | |
* | |
* render() { | |
* return ( | |
* <section ref={r => { this.section = r; }}> | |
* Child1 | |
* </section> | |
* ); | |
* } | |
* } | |
* | |
* ... | |
* | |
* | |
* OTHER POSIBILITIES: | |
* ---------------------- | |
* | |
* You can also overwrite the show/hide methods to do what you whant. Ex : | |
* | |
* show() { | |
* this.updateAnim('childchild', true).then(() => this.timelineIn.play); | |
* } | |
* | |
* hide() { | |
* this.timelineIn.reverse(0); | |
* } | |
* | |
**/ | |
import React, { Component } from 'react'; | |
import PropTypes from 'prop-types'; | |
import { TimelineLite } from 'gsap'; | |
const TM_RESOLVER = 'fireComplete'; | |
export default class AnimComponent extends Component { | |
constructor(props) { | |
super(props); | |
this._shown = false; | |
this._animComplete = true; | |
this.anims = {}; | |
this.timelineIn = new TimelineLite({ paused: true }); | |
this.timelineOut = new TimelineLite({ paused: true }); | |
this._oldPosition = 0; | |
this._oldCallback = false; | |
this.updateAnim = this.updateAnim.bind(this); | |
this._setAnim = this._setAnim.bind(this); | |
this._testAnim = this._testAnim.bind(this); | |
this.show = this.show.bind(this); | |
this.hide = this.hide.bind(this); | |
} | |
componentDidMount() { | |
this._testAnim(); | |
} | |
componentDidUpdate() { | |
this._testAnim(); | |
} | |
componentWillUnmount() {} | |
/** | |
* Fired when the component is updated and test the anim property. | |
* If this one exist and is different to the last time, | |
* she updates the timelines to add callback at the good position | |
* and launch `show` or `hide` method. | |
*/ | |
_testAnim() { | |
const { anim } = this.props; | |
if (!anim || typeof anim.shown !== 'boolean' || typeof anim.callback !== 'function') { | |
return; | |
} | |
if (anim.shown !== this._shown) { | |
this._shown = anim.shown; | |
this._animComplete = false; | |
/** *********************** | |
* Sets and updates the callback on the two timelines. | |
* It's for both because `show()` and `hide()` methods can be overwritten by the user. | |
*/ | |
// Stops and removes the old callback to the timelines | |
this.timelineIn.pause(); | |
this.timelineOut.pause(); | |
this.timelineIn.remove(this._oldCallback, this._oldPosition); | |
this.timelineOut.remove(this._oldCallback, this._oldPosition); | |
// Set the new callback to a new position | |
this.timelineIn.add(anim.callback, anim.position); | |
this.timelineOut.add(anim.callback, anim.position); | |
// Save the params to remove them the next time | |
this._oldPosition = anim.position; | |
this._oldCallback = anim.callback; | |
// TODO the HACK for a timeline.reverse() usage must be remplaced. | |
this.timelineIn.eventCallback('onReverseComplete', anim.callback); | |
this.timelineOut.eventCallback('onReverseComplete', anim.callback); | |
// Launch the animation | |
if (anim.shown) { | |
this.show(anim.payload); | |
} else { | |
this.hide(anim.payload); | |
} | |
} | |
} | |
/** | |
* Update an child anim status | |
* | |
* @type {String} name ... The name of the child component. | |
* @type {Object} object . The data to update | |
*/ | |
_setAnim(name, object) { | |
this.anims[name] = Object.assign(this.anims[name] || {}, object); | |
} | |
/** | |
* ************ | |
* PARENT CONTROLS | |
* ************ | |
*/ | |
/** | |
* Creates a promise and updates the children animation status managed through `this.anims` | |
* to start the animation of one animComponent child. | |
* The Promise is resolved when the child's animation is ended. | |
* | |
* @type {String} name ...... The name of the child component. | |
* @type {Boolean} shown .... The new status of the child component. | |
* @type {Object} payload ... ??? | |
* @type {String} position .. Same utility as a Timeline animation. | |
*/ | |
updateAnim(name, shown, payload = {}, position = '+=0') { | |
return new Promise((resolve) => { | |
// Callback who resolve the Promise and show the animComplete status. | |
const callback = (object = {}) => { | |
this._animComplete = true; | |
this._setAnim(name, Object.assign(object, { animComplete: true })); | |
resolve(); | |
}; | |
// TEST IF HE HAVE THE SAME UPDATE | |
if ( | |
// is never appeared and he want to be hidden | |
(this.anims[name] === undefined && shown === false) || | |
// or is already appeared and have the same update | |
(this.anims[name] !== undefined && this.anims[name].shown === shown) | |
) { | |
callback({ shown, payload }); | |
return; | |
} | |
// Launch the animation | |
this._setAnim(name, { shown, callback, payload, position, animComplete: false }); | |
this.forceUpdate(); | |
}); | |
} | |
/** | |
* ************ | |
* SHOW / HIDE | |
* ************ | |
* Could be overwritten by the extended class | |
*/ | |
show() { | |
this.timelineIn.play(0); | |
} | |
hide() { | |
this.timelineOut.play(0); | |
} | |
/** | |
* ************ | |
* TEST | |
* ************ | |
*/ | |
isHidden() { | |
return !this._shown && this._animComplete; | |
} | |
/** | |
* If a child is completly hidden after the animation | |
*/ | |
childIsHidden(name) { | |
return (this.anims[name] === undefined) | |
|| (!this.anims[name].shown && this.anims[name].animComplete) | |
; | |
} | |
} | |
AnimComponent.propTypes = { | |
anim: PropTypes.shape({ | |
shown: PropTypes.bool, | |
resolve: PropTypes.func, | |
}), | |
}; | |
AnimComponent.defaultProps = { | |
anim: {}, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment