Skip to content

Instantly share code, notes, and snippets.

@elierotenberg
Created July 25, 2014 09:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elierotenberg/79d46fcbb60e1ea0d53b to your computer and use it in GitHub Desktop.
Save elierotenberg/79d46fcbb60e1ea0d53b to your computer and use it in GitHub Desktop.
Animating with React and d3
var _ = require("lodash");
var d3 = require("d3");
var raf = require("raf");
var getInterpolator = function getInterpolator(from, to) {
return d3.interpolate(from, to);
};
var startInterpolation = function startInterpolation(properties, easing, duration, onTick, onComplete) {
var aborted = false;
var nextTick = null;
var interpolators = {};
_.each(properties, function(specs, name) {
interpolators[name] = getInterpolator(specs.from, specs.to);
});
var ease;
if(_.isObject(easing)) {
assert(easing.type);
assert(easing.arguments);
ease = d3.ease.apply(d3, _.union([easing.type], easing.arguments));
}
else {
ease = d3.ease(easing);
}
var abort = function abort() {
aborted = true;
if(nextTick) {
raf.cancel(nextTick);
}
};
var start = Date.now();
var end = start + duration;
var tick = function tick() {
if(aborted) {
return;
}
else {
var now = Date.now();
var t = (now - start)/(end - start);
if(t > 1) {
var finalProperties = {};
_.each(properties, function(specs, name) {
finalProperties[name] = specs.to;
});
onTick(finalProperties);
_.defer(onComplete);
return;
}
else {
var tickProperties = {};
_.each(_.keys(properties), function(name) {
tickProperties[name] = interpolators[name](ease(t));
});
onTick(tickProperties);
nextTick = raf(tick);
}
}
};
tick();
return {
abort: abort
};
};
module.exports = {
getInterpolator: getInterpolator,
startInterpolation: startInterpolation,
};
var _ = require("lodash");
var assert = require("assert");
var Animate = require("../lib/Animate");
var AnimateMixin = {
componentWillMount: function componentWillMount() {
this._currentAnimation = {};
},
componentWillUnmount: function componentWillUnmount() {
this._currentAnimation = null;
},
_currentAnimation: null,
_updateAnimatedStyle: function updateAnimatedStyle(key, properties) {
this.setState(_.object([
["_animatedStyle:" + key, properties],
]));
},
animate: function animate(key, properties, easing, duration, onComplete) {
onComplete = onComplete || _.identity;
var self = this;
if(_.has(this._currentAnimation, key)) {
this._currentAnimation[key].abort();
}
this._currentAnimation[key] = Animate.startInterpolation(properties, easing, duration, function(animatedStyle) {
self._updateAnimatedStyle(key, animatedStyle);
}, onComplete);
},
abortAnimation: function abortAnimation(key) {
assert(this._currentAnimation[key]);
this._currentAnimation[key].abort();
},
getAnimatedStyle: function getAnimatedStyle(key) {
return this.state["_animatedStyle:" + key];
},
};
module.exports = AnimateMixin;
/** @jsx React.DOM */
var React = require("react/addons");
var _ = require("lodash");
var ChannelItem = require("./ChannelItem");
var PureComponent = require("../lib/PureComponent");
var AnimateMixin = require("../mixins/AnimateMixin");
var itemHeight = 66;
var ChannelList = React.createClass({
mixins: [AnimateMixin],
getInitialState: function getInitialState() {
return {
collapsed: false,
};
},
toggleCollapse: function toggleCollapse() {
var totalHeight = this.props.collection.length * itemHeight;
this.animate("collapse", {
height: this.state.collapsed ? {
from: 0,
to: totalHeight,
} : {
from: totalHeight,
to: 0,
},
}, "cubic-in-out", 500);
this.setState({
collapsed: !this.state.collapsed,
});
},
onTransitionEnd: function onTransitionEnd() {
this.setState({
transitionning: false,
collapsed: !this.state.collapsed,
});
},
shouldComponentUpdate: PureComponent.shouldComponentUpdate,
render: function render() {
return (
<li className="ChannelList">
<h3 onClick={this.toggleCollapse}>{this.props.name}</h3>
<ul style={_.extend({
overflow: "hidden",
}, this.getAnimatedStyle("collapse"))}>
{
_.map(this.props.collection, function(scheduleHref, key) {
return (
<li key={key} style={{ height: itemHeight }}>
<ChannelItem scheduleHref={scheduleHref.self} />
</li>
);
})
}
</ul>
</li>
);
},
});
module.exports = ChannelList;
var _ = require("lodash");
var shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) {
return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
};
module.exports = {
shouldComponentUpdate: shouldComponentUpdate,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment