@tidepool/viz's usage of GSAP
GSAP (GreenSock Animation Platform) is a powerful library for animating HTML5 documents with JavaScript (largely as an alternative to CSS3). GSAP as a product started with a similar animation library for Flasha. The now more widely used JavaScript library has an extensive and yet still fairly intuitive API that allows for finely-grained control of animations, including an API for sequencing within complex animations that is far over and above what can be accomplished with CSS3's @keyframes
. The library is available in several separate modules (TweenLite, TweenMax, TimelineLite, TimelineMax) to keep file sizes small (choose what you need), and even the combination of TweenMax + TimelineMax is less than 200KB minified (that's about the size of React + ReactDOM but much less than Angular 2b).
📚 resources
- GSAP docs
- Advanced SVG Animation, a Frontend Masters course by Sarah Drasner
🕰️ when we use GSAP
Our first choice for animating with React is React Motion, but we fall back to GSAP where React Motion's API doesn't provide what we need. So far the only circumstance that we've needed to pull GSAP in for is a staggered animation on revealing the daily CGM sensor traces via hover over a CGM time-slice segment in the CGM Trends view. This is due to the fact that React Motion's StaggeredMotion
component only serves a subset of stagger animations—namely, those in which each value in the stagger is entirely dependent on the previous and next values. In the case of a CGM sensor trace for a day, while each value is somewhat dependent on its previous value, CGM values fail the entirely dependent criterion: while blood glucose can't jump from 68 mg/dL to 345 mg/dL in the space of a single five-minute span of time, all blood glucose values do fall within a certain "delta" of the previous value, though this delta varies (i.e., the previous and next values are not entirely dependent on each other).
🍝 integrating GSAP with React
The strategy for using GSAP with React is very similar to the componentDidMount
strategy for integrating React and D3 but perhaps a little less offensive to the React purist. Instead of rendering to the DOM in the componentDidMount
lifecycle method (after React's render cycle), to integrate GSAP with React we first render the component inside a ReactTransitionGroup
container. Then in the componentWillEnter
method (which is provided by ReactTransitionGroups
and fires at the same point in the React lifecycle as componentDidMount
) we simply gather the references to DOM nodes that have just been rendered by React and then pass them to GSAP as the target(s) for animation. We do the same on exit only inside the componentWillLeave
method also provided by ReactTransitionGroup
. In this way, we're only modifying the appearance of DOM nodes via GSAP and not disrupting React's control of rendering to the DOM. (In addition, since animations are more-or-less a nice-to-havec and not absolutely essential to our data visualization functionality, we don't write tests around them, and so the difficulty of writing tests around things that happen inside React lifecycle methods does not come into play.)
✍️ example
If your React component has rendered a series of SVG <circle>
s that you want to reveal in a stagger animation, one-by-one, start with assigning a ref
on each <circle>
in the render
method of the component:
render() {
return (
<g id="a-gaggle-of-circles">
{_.map(data, (d) => (
<circle cx={5} cy={10} r={5} opacity={0} ref={(node) => { this[d.id] = node; }} />
))}
</g>
);
}
Notice that we also render initially with opacity zero; the trick to this staggered render of <circle>
s is to render all the <circle>
s at the same time (in the React component's render
method) and then stagger the animation to full opacity in componentWillEnter
:
componentWillEnter(callback) {
const { animationDuration, data } = this.props;
const targets = _.map(data, (d) => (this[d.id]));
TweenMax.staggerTo(
targets, animationDuration, { opacity: 1, onComplete: callback }, animationDuration / targets.length
);
}
Also note the callback argument provided to componentWillEnter
, which we specify as the onComplete
in the TweenMax.staggerTo
configuration.
a. Link, of course, for the youngins soon to ask, "What the hell is Flash?" (Also, 🎩 to @krystophv for pointing out to @jebeck that GSAP was originally a Flash animation library.) ↩
b. Source for React + React DOM and Angular 2 sizes: https://gist.github.com/Restuta/cda69e50a853aa64912d ↩
c. Though very valuable for context-shifting, which is arguably even more important in data visualization than general application UX, because context-shifting with data aids in understanding changes and relationships in data, which is often (if not always!) the primary communicative purpose of a data visualization. ↩