Skip to content

Instantly share code, notes, and snippets.

@prenaux
Created August 30, 2015 06:55
Show Gist options
  • Select an option

  • Save prenaux/5604df98e7855e960fb4 to your computer and use it in GitHub Desktop.

Select an option

Save prenaux/5604df98e7855e960fb4 to your computer and use it in GitHub Desktop.
An updated MagicMove.jsx, faster than react-shuffle, but has issues with overlays and resizing, atm I'm using react-shuffle because of that.
var React = require('react');
var cloneWithProps = require('react/lib/cloneWithProps');
var Clones = React.createClass({
displayName: 'MagicMoveClones',
propTypes: {
children: React.PropTypes.node,
},
childrenWithPositions: function childrenWithPositions() {
var that = this;
var children = [];
React.Children.forEach(this.props.children, function (child) {
var style = that.props.positions[child.key];
var key = child.key;
children.push(cloneWithProps(child, { style: style, key: key }));
});
return children.sort(function (a, b) {
return (a.key < b.key) ? (-1) : ((a.key > b.key) ? 1 : 0);
});
},
render: function render() {
return React.createElement(
'div',
{ className: 'MagicMoveClones' },
this.childrenWithPositions()
);
}
});
var MagicMove = React.createClass({
displayName: 'MagicMove',
propTypes: {
positions: React.PropTypes.array,
children: React.PropTypes.node,
},
getInitialState: function getInitialState() {
return {
animating: false
};
},
componentDidMount: function componentDidMount() {
this.makePortal();
this.renderClonesInitially();
},
componentWillUnmount: function componentWillUnmount() {
document.body.removeChild(this.portalNode);
},
componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
this.startAnimation(nextProps);
},
componentDidUpdate: function componentDidUpdate(prevProps) {
if (this.state.animating) {
this.renderClonesToNewPositions(prevProps);
}
},
makePortal: function makePortal() {
this.portalNode = document.createElement('div');
this.portalNode.style.left = '-9999px';
document.body.appendChild(this.portalNode);
},
addTransitionEndEvent: function addTransitionEndEvent() {
// if you click RIGHT before the transition is done, the animation jumps,
// its because the transitionend event fires even though its not quite
// done, not sure how to hack around it yet.
this._transitionHandler = callOnNthCall(this.props.children.length, this.finishAnimation);
this.portalNode.addEventListener('transitionend', this._transitionHandler);
},
removeTransitionEndEvent: function removeTransitionEndEvent() {
this.portalNode.removeEventListener('transitionend', this._transitionHandler);
},
startAnimation: function startAnimation(nextProps) {
var that = this;
if (this.state.animating) {
return;
}
this.addTransitionEndEvent();
nextProps.animating = true;
nextProps.positions = this.getPositions();
this.renderClones(nextProps, function () {
that.setState({ animating: true });
});
},
renderClonesToNewPositions: function renderClonesToNewPositions(prevProps) {
prevProps.positions = this.getPositions();
this.renderClones(prevProps);
},
finishAnimation: function finishAnimation() {
this.removeTransitionEndEvent();
this.portalNode.style.position = 'absolute';
this.portalNode.style.top = '0'; // PIERRE: required so that an unneeded scrollbar isn't created
this.setState({ animating: false });
},
getPositions: function getPositions() {
var that = this;
var positions = {};
React.Children.forEach(this.props.children, function (child) {
var ref = child.key;
var node = that.refs[ref].getDOMNode();
var rect = node.getBoundingClientRect();
var computedStyle = getComputedStyle(node);
var marginTop = parseInt(computedStyle.marginTop, 10);
var marginLeft = parseInt(computedStyle.marginLeft, 10);
var position = {
top: (rect.top - marginTop) + window.scrollY,
left: (rect.left - marginLeft) + window.scrollX,
width: rect.width,
height: rect.height,
position: 'absolute'
};
positions[ref] = position;
});
return positions;
},
renderClonesInitially: function renderClonesInitially() {
this.props.positions = this.getPositions();
React.render(React.createElement(Clones, this.props), this.portalNode);
},
renderClones: function renderClones(props, cb) {
this.portalNode.style.position = '';
React.render(React.createElement(Clones, props), this.portalNode, cb);
},
childrenWithRefs: function childrenWithRefs() {
return React.Children.map(this.props.children, function (child) {
return cloneWithProps(child, { ref: child.key });
});
},
render: function render() {
var style = {
opacity: this.state.animating ? 0 : 1,
};
return React.createElement(
'div',
{ style: style },
this.childrenWithRefs()
);
}
});
function callOnNthCall(n, fn) {
var calls = 0;
return function () {
calls++;
if (calls === n) {
calls = 0;
return fn.apply(this, arguments);
}
};
}
module.exports = MagicMove;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment