Skip to content

Instantly share code, notes, and snippets.

@andrewgleave
Last active November 27, 2018 20:41
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save andrewgleave/3c19fdaeeff0c64f20f4 to your computer and use it in GitHub Desktop.
Save andrewgleave/3c19fdaeeff0c64f20f4 to your computer and use it in GitHub Desktop.
Keyframe animation support for React. Enables integration between React components and JS animation events.
/** @jsx React.DOM */
'use strict';
var React = require('react');
var AnimatableComponent = React.createClass({
propTypes: {
tag: React.PropTypes.component.isRequired,
animationClass: React.PropTypes.string.isRequired,
key: React.PropTypes.string,
shouldStartAnimation: React.PropTypes.bool,
removeOnComplete: React.PropTypes.bool,
className: React.PropTypes.string,
onAnimationStart: React.PropTypes.func,
onAnimationIteration: React.PropTypes.func,
onAnimationEnd: React.PropTypes.func
},
getDefaultProps: function() {
return {
tag: React.DOM.div,
animationClass: '',
className: '',
removeOnComplete: true
};
},
getInitialState: function() {
return {
applyAnimationClass: false
};
},
componentWillMount: function() {
this.isAnimating = false;
this.animationStartTime = 0;
this.animationElapsedTime = 0;
this.animationIterationCount = 0;
},
componentDidMount: function() {
var node = this.refs.el.getDOMNode();
this.prefixEventHandler(node, 'AnimationStart', this.handleAnimationStart);
this.prefixEventHandler(node, 'AnimationEnd', this.handleAnimationEnd);
this.prefixEventHandler(node, 'AnimationIteration', this.handleAnimationIteration);
},
componentWillReceiveProps: function(nextProps) {
this.setState({
applyAnimationClass: nextProps.shouldStartAnimation
});
},
handleAnimationStart: function(e) {
if(e.animationClass === this.props.animationClass) {
this.isAnimating = true;
this.animationStartTime = Date.now();
this.animationElapsedTime = 0;
this.animationIterationCount = 0;
e.stopPropagation();
if(this.props.onAnimationStart) {
this.props.onAnimationStart(this, e);
}
}
},
handleAnimationIteration: function(e) {
if(e.animationClass === this.props.animationClass) {
e.stopPropagation();
this.animationIterationCount += 1;
this.animationElapsedTime = e.elapsedTime;
if(this.props.onAnimationIteration) {
this.props.onAnimationIteration(this, e);
}
}
},
handleAnimationEnd: function(e) {
if(e.animationClass === this.props.animationClass) {
e.stopPropagation();
this.isAnimating = false;
this.animationElapsedTime = e.elapsedTime;
if(this.props.removeOnComplete) {
this.setState({ applyAnimationClass: false });
}
if(this.props.onAnimationEnd) {
this.props.onAnimationEnd(this, e);
}
}
},
prefixEventHandler: function(node, name, handler, remove) {
var prefixes = ['webkit', 'moz', 'MS', 'o', ''];
for(var i = 0; i < prefixes.length; i++) {
var eventName = (prefixes[i] === '') ? name.toLowerCase() : prefixes[i] + name;
if(!remove) {
node.addEventListener(eventName, handler);
}
else {
node.removeEventListener(eventName, handler);
}
}
},
componentWillUnmount: function() {
var node = this.refs.el.getDOMNode();
this.prefixEventHandler(node, 'AnimationStart', this.handleAnimationStart, true);
this.prefixEventHandler(node, 'AnimationEnd', this.handleAnimationEnd, true);
this.prefixEventHandler(node, 'AnimationIteration', this.handleAnimationIteration, true);
},
render: function() {
var fullClass = this.props.className;
if(this.state.applyAnimationClass) {
fullClass += ' ' + this.props.animationClass;
}
return ( this.props.tag({
ref: 'el',
className: fullClass.trim(),
key: this.props.key
}, this.props.children ));
}
});
module.exports = AnimatableComponent;
/** @jsx React.DOM */
'use strict';
var _ = require('lodash');
var React = require('react');
var api = require('./api.js');
var AnimatableComponent = require('./AnimatableComponent.js');
var TableView = React.createClass({
getInitialState: function() {
return {
items: [],
updatedItem: null
};
},
componentWillMount: function() {
this.load();
},
load: function() {
api.load(function(res) {
if(res.ok && !res.noContent) {
this.setState({ items: res.body });
}
}.bind(this));
},
handleAnimationIteration: function(item, e) {
console.log('Item ' + item.id + ' animation iteration count: ' + item.animationIterationCount);
},
handleAnimationStart: function(item, e) {
console.log('Item ' + item.id + ' is animating');
},
handleAnimationStop: function(item, e) {
console.log('Item ' + item.id + ' has stopped animating');
},
render: function() {
var rows = _.map(this.state.items, function(item) {
return (
<AnimatableComponent
tag={ React.DOM.tr }
className="table-row"
animationClass="flash"
shouldStartAnimation={ item === this.updatedItem }
onAnimationStart={ this.handleAnimationStart }
onAnimationIteration={ this.handleAnimationIteration }
onAnimationStop={ this.handleAnimationStop }
removeOnComplete={ true }
key={ 'row-' + item.id }>
<td>{ item.A }</td>
<td>{ item.B }</td>
<td>{ item.C }</td>
<td>{ item.D }</td>
</AnimatableComponent>
);
}).bind(this);
return (
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
</tr>
</thead>
{ rows }
</table>
);
}
});
module.exports = TableView;
@DenisIzmaylov
Copy link

What are key differences from http://facebook.github.io/react/docs/animation.html ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment