From 43e2691f30a67349a8d33d8cbf14398664602cbd Mon Sep 17 00:00:00 2001 From: Tom Gasson Date: Thu, 11 Feb 2016 15:37:40 +1100 Subject: [PATCH 1/3] Use react-motion rather than react-twean-state --- package.json | 2 +- src/shuffle.js | 286 +++++++++++++------------------------------------ 2 files changed, 78 insertions(+), 210 deletions(-) diff --git a/package.json b/package.json index e60496c..5922508 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dependencies": { "object-assign": "^4.0.1", "react-addons-transition-group": "^0.14.0", - "react-tween-state": "^0.1.3" + "react-motion": "^0.4.2" }, "peerDependencies": { "react": "0.14.x" diff --git a/src/shuffle.js b/src/shuffle.js index d732a33..b641090 100644 --- a/src/shuffle.js +++ b/src/shuffle.js @@ -3,125 +3,18 @@ import React from 'react'; import ReactDom from 'react-dom'; -import assign from 'object-assign'; -import tweenState from 'react-tween-state'; -import ReactTransitionGroup from 'react-addons-transition-group'; - -const Clones = React.createClass({ - displayName: 'ShuffleClones', - - _childrenWithPositions() { - let children = []; - React.Children.forEach(this.props.children, (child) => { - let style = this.props.positions[child.key]; - let key = child.key; - children.push(); - }); - return children.sort((a, b) => - (a.key < b.key) ? -1 : (a.key > b.key) ? 1 : 0 - ); - }, - - render() { - return ( -
- - {this._childrenWithPositions()} - -
- ); - } -}); - -const Clone = React.createClass({ - mixins: [tweenState.Mixin], - displayName: 'ShuffleClone', - getInitialState() { - return { - top: this.props.style ? this.props.style.top : 0, - left: this.props.style ? this.props.style.left : 0, - opacity: 1, - transform: 1 - } - }, - componentWillAppear(cb) { - this.tweenState('opacity', { - easing: tweenState.easingTypes.easeOutSine, - duration: this.props.duration, - beginValue: this.props.initial ? 0 : 1, - endValue: 1, - onEnd: cb - }); - }, - componentWillEnter(cb) { - this.tweenState('opacity', { - easing: tweenState.easingTypes.easeOutSine, - duration: this.props.duration, - beginValue: 0, - endValue: 1, - onEnd: cb - }); - }, - componentWillLeave(cb) { - this.tweenState('opacity', { - easing: tweenState.easingTypes.easeOutSine, - duration: this.props.duration, - endValue: 0, - onEnd: () => { - try { - cb() - } catch (e) { - // This try catch handles component umounting jumping the gun - } - } - }); - }, - componentWillReceiveProps(nextProps) { - this.tweenState('top', { - easing: tweenState.easingTypes.easeOutSine, - duration: nextProps.duration, - endValue: nextProps.style.top - }); - this.tweenState('left', { - easing: tweenState.easingTypes.easeOutSine, - duration: nextProps.duration, - endValue: nextProps.style.left - }); - }, - render() { - let style = {}; - if (this.props.style) { - style = { - top: this.getTweeningValue('top'), - left: this.getTweeningValue('left'), - opacity: this.props.fade ? this.getTweeningValue('opacity') : 1, - transform: this.props.scale ? 'scale(' + this.getTweeningValue('opacity') + ')' : 0, - transformOrigin: 'center center', - width: this.props.style.width, - height: this.props.style.height, - position: this.props.style.position - }; - } - let key = this.props.key; - return ( - React.cloneElement(this.props.child, {style, key}) - ) - } -}) +import { TransitionMotion, spring } from 'react-motion' +import stripStyle from 'react-motion/lib/stripStyle' const Shuffle = React.createClass({ displayName: 'Shuffle', propTypes: { - duration: React.PropTypes.number, + springConfig: React.PropTypes.shape({ + stiffness: React.PropTypes.number, + damping: React.PropTypes.number + }), scale: React.PropTypes.bool, fade: React.PropTypes.bool, initial: React.PropTypes.bool @@ -129,42 +22,25 @@ const Shuffle = React.createClass({ getDefaultProps() { return { - duration: 300, + springConfig: undefined, // uses react-motion default scale: true, fade: true, initial: false } }, - - getInitialState() { - return { - animating: false, - ready: false - }; - }, - componentDidMount() { this._makePortal(); - window.addEventListener('resize', this._renderClonesInitially); + window.addEventListener('resize', this._renderClones); + this._renderClones(); }, componentWillUnmount() { ReactDom.findDOMNode(this.refs.container).removeChild(this._portalNode); - window.removeEventListener('resize', this._renderClonesInitially); - }, - - componentWillReceiveProps(nextProps) { - this._startAnimation(nextProps); + window.removeEventListener('resize', this._renderClones); }, - componentDidUpdate(prevProps) { - if (this.state.ready === false) { - this.setState({ready: true}, () => { - this._renderClonesInitially(); - }); - } else { - this._renderClonesToNewPositions(this.props); - } + componentDidUpdate() { + this._renderClones(); }, _makePortal() { @@ -175,95 +51,87 @@ const Shuffle = React.createClass({ ReactDom.findDOMNode(this.refs.container).appendChild(this._portalNode); }, - _addTransitionEndEvent() { - setTimeout(this._finishAnimation, this.props.duration); - }, - - _startAnimation(nextProps) { - if (this.state.animating) { - return; - } - - let cloneProps = assign({}, nextProps, { - positions: this._getPositions(), - initial: this.props.initial, - fade: this.props.fade, - scale: this.props.scale, - duration: this.props.duration - }); - this._renderClones(cloneProps, () => { - this._addTransitionEndEvent(); - this.setState({animating: true}); - }); - }, - - _renderClonesToNewPositions(props) { - let cloneProps = assign({}, props, { - positions: this._getPositions(), - initial: this.props.initial, - fade: this.props.fade, - scale: this.props.scale, - duration: this.props.duration - }); - this._renderClones(cloneProps); - }, - - _finishAnimation() { - this.setState({animating: false}); - }, - - _getPositions() { - let positions = {}; - React.Children.forEach(this.props.children, (child) => { + _renderClones() { + let styles = [] + let defaultStyles = [] + React.Children.forEach(this.props.children, child => { let ref = child.key; let node = ReactDom.findDOMNode(this.refs[ref]); let rect = node.getBoundingClientRect(); let computedStyle = getComputedStyle(node); let marginTop = parseInt(computedStyle.marginTop, 10); let marginLeft = parseInt(computedStyle.marginLeft, 10); - let position = { - top: (node.offsetTop - marginTop), - left: (node.offsetLeft - marginLeft), - width: rect.width, - height: rect.height, - position: 'absolute', - opacity: 1 - }; - positions[ref] = position; - }); - return positions; - }, - _renderClonesInitially() { - let cloneProps = assign({}, this.props, { - positions: this._getPositions(), - initial: this.props.initial, - fade: this.props.fade, - scale: this.props.scale, - duration: this.props.duration - }); - ReactDom.render(, this._portalNode); - this.setState({ready: true}); - }, - - _renderClones(props, cb) { - ReactDom.render(, this._portalNode, cb); + styles.push({ + key: child.key, + style: { + width: spring(rect.width, this.props.springConfig), + height: spring(rect.height, this.props.springConfig), + left: spring(node.offsetLeft - marginLeft, this.props.springConfig), + top: spring(node.offsetTop - marginTop, this.props.springConfig), + scale: this.props.scale ? spring(1) : 1, + opacity: this.props.fade ? spring(1) : 1 + }, + data: child + }) + defaultStyles.push({ + key: child.key, + style: { + width: rect.width, + height: rect.height, + left: node.offsetLeft - marginLeft, + top: node.offsetTop - marginTop, + scale: this.props.scale ? 0 : 1, + opacity: this.props.fade ? 0 : 1 + }, + data: child + }) + }) + + ReactDom.render(( + ({ + ...style.style, + opacity:spring(0, this.props.springConfig), + scale:spring(0, this.props.springConfig)} + )} + willEnter={style => ({ + ...stripStyle(style.style), + opacity:0, + scale:0 + })} + defaultStyles={this.props.initial?defaultStyles:null} + styles={styles}> + {interpolatedStyles => +
+ {interpolatedStyles.map(config => { + return React.cloneElement(config.data, { + key: config.key, + style: { + position: 'absolute', + width: config.style.width, + height: config.style.height, + left: config.style.left, + top: config.style.top, + opacity: config.style.opacity, + transform: `scale(${config.style.scale})` + } + }) + })} +
+ } +
+ ), this._portalNode); }, - _childrenWithRefs() { return React.Children.map(this.props.children, (child) => React.cloneElement(child, {ref: child.key}) ); }, - render() { - var showContainer = this.props.initial ? 0 : 1; - if (this.state.ready) { - showContainer = 0; - } return (
-
+
{this._childrenWithRefs()}
From 4698943752c39ad5516b691b3c4744ff5a741f44 Mon Sep 17 00:00:00 2001 From: Tom Gasson Date: Fri, 12 Feb 2016 09:30:44 +1100 Subject: [PATCH 2/3] add scaleConfig documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01f3e59..83e311f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Simply wrap child components with this component and dynamically change them to | Prop | PropType | Description | | ---- | -------- | ----------- | -| duration | React.PropTypes.number | Duration of animation | +| springConfig | React.PropTypes.object | A {stiffness, damping} for react-motion ([details](https://github.com/chenglou/react-motion#--spring-val-number-config-springhelperconfig--opaqueconfig)) | | fade | React.PropTypes.bool | Should children fade on enter/leave | | scale | React.PropTypes.bool | Should children scale on enter/leave | | intial | React.PropTypes.bool | Should scale/fade occur on first load | From bfa13f200cab8c1cf26b97a728535d30afaab034 Mon Sep 17 00:00:00 2001 From: Tom Gasson Date: Mon, 22 Feb 2016 17:21:47 +1100 Subject: [PATCH 3/3] context and refs fix --- lib/shuffle.js | 319 +++++++++++++++---------------------------------- src/shuffle.js | 19 ++- 2 files changed, 109 insertions(+), 229 deletions(-) diff --git a/lib/shuffle.js b/lib/shuffle.js index f5871f8..bec849f 100644 --- a/lib/shuffle.js +++ b/lib/shuffle.js @@ -19,135 +19,21 @@ var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); -var _objectAssign = require('object-assign'); +var _reactMotion = require('react-motion'); -var _objectAssign2 = _interopRequireDefault(_objectAssign); +var _reactMotionLibStripStyle = require('react-motion/lib/stripStyle'); -var _reactTweenState = require('react-tween-state'); - -var _reactTweenState2 = _interopRequireDefault(_reactTweenState); - -var _reactAddonsTransitionGroup = require('react-addons-transition-group'); - -var _reactAddonsTransitionGroup2 = _interopRequireDefault(_reactAddonsTransitionGroup); - -var Clones = _react2['default'].createClass({ - displayName: 'ShuffleClones', - - _childrenWithPositions: function _childrenWithPositions() { - var _this = this; - - var children = []; - _react2['default'].Children.forEach(this.props.children, function (child) { - var style = _this.props.positions[child.key]; - var key = child.key; - children.push(_react2['default'].createElement(Clone, { - child: child, - style: style, - key: key, - initial: _this.props.initial, - fade: _this.props.fade, - scale: _this.props.scale, - duration: _this.props.duration })); - }); - return children.sort(function (a, b) { - return a.key < b.key ? -1 : a.key > b.key ? 1 : 0; - }); - }, - - render: function render() { - return _react2['default'].createElement( - 'div', - { className: 'ShuffleClones' }, - _react2['default'].createElement( - _reactAddonsTransitionGroup2['default'], - null, - this._childrenWithPositions() - ) - ); - } -}); - -var Clone = _react2['default'].createClass({ - mixins: [_reactTweenState2['default'].Mixin], - displayName: 'ShuffleClone', - getInitialState: function getInitialState() { - return { - top: this.props.style ? this.props.style.top : 0, - left: this.props.style ? this.props.style.left : 0, - opacity: 1, - transform: 1 - }; - }, - componentWillAppear: function componentWillAppear(cb) { - this.tweenState('opacity', { - easing: _reactTweenState2['default'].easingTypes.easeOutSine, - duration: this.props.duration, - beginValue: this.props.initial ? 0 : 1, - endValue: 1, - onEnd: cb - }); - }, - componentWillEnter: function componentWillEnter(cb) { - this.tweenState('opacity', { - easing: _reactTweenState2['default'].easingTypes.easeOutSine, - duration: this.props.duration, - beginValue: 0, - endValue: 1, - onEnd: cb - }); - }, - componentWillLeave: function componentWillLeave(cb) { - this.tweenState('opacity', { - easing: _reactTweenState2['default'].easingTypes.easeOutSine, - duration: this.props.duration, - endValue: 0, - onEnd: function onEnd() { - try { - cb(); - } catch (e) { - // This try catch handles component umounting jumping the gun - } - } - }); - }, - componentWillReceiveProps: function componentWillReceiveProps(nextProps) { - this.tweenState('top', { - easing: _reactTweenState2['default'].easingTypes.easeOutSine, - duration: nextProps.duration, - endValue: nextProps.style.top - }); - this.tweenState('left', { - easing: _reactTweenState2['default'].easingTypes.easeOutSine, - duration: nextProps.duration, - endValue: nextProps.style.left - }); - }, - render: function render() { - var style = {}; - if (this.props.style) { - style = { - top: this.getTweeningValue('top'), - left: this.getTweeningValue('left'), - opacity: this.props.fade ? this.getTweeningValue('opacity') : 1, - transform: this.props.scale ? 'scale(' + this.getTweeningValue('opacity') + ')' : 0, - transformOrigin: 'center center', - width: this.props.style.width, - height: this.props.style.height, - position: this.props.style.position - }; - } - var key = this.props.key; - return _react2['default'].cloneElement(this.props.child, { style: style, key: key }); - } -}); +var _reactMotionLibStripStyle2 = _interopRequireDefault(_reactMotionLibStripStyle); var Shuffle = _react2['default'].createClass({ displayName: 'Shuffle', propTypes: { - duration: _react2['default'].PropTypes.number, + springConfig: _react2['default'].PropTypes.shape({ + stiffness: _react2['default'].PropTypes.number, + damping: _react2['default'].PropTypes.number + }), scale: _react2['default'].PropTypes.bool, fade: _react2['default'].PropTypes.bool, initial: _react2['default'].PropTypes.bool @@ -155,44 +41,27 @@ var Shuffle = _react2['default'].createClass({ getDefaultProps: function getDefaultProps() { return { - duration: 300, + springConfig: undefined, // uses react-motion default scale: true, fade: true, initial: false }; }, - - getInitialState: function getInitialState() { - return { - animating: false, - ready: false - }; - }, - componentDidMount: function componentDidMount() { this._makePortal(); - window.addEventListener('resize', this._renderClonesInitially); + window.addEventListener('resize', this._renderClones); + this._renderClones(); }, componentWillUnmount: function componentWillUnmount() { - _reactDom2['default'].findDOMNode(this.refs.container).removeChild(this._portalNode); - window.removeEventListener('resize', this._renderClonesInitially); - }, - - componentWillReceiveProps: function componentWillReceiveProps(nextProps) { - this._startAnimation(nextProps); + if (this.container !== null) { + this.container.removeChild(this._portalNode); + } + window.removeEventListener('resize', this._renderClones); }, - componentDidUpdate: function componentDidUpdate(prevProps) { - var _this2 = this; - - if (this.state.ready === false) { - this.setState({ ready: true }, function () { - _this2._renderClonesInitially(); - }); - } else { - this._renderClonesToNewPositions(this.props); - } + componentDidUpdate: function componentDidUpdate() { + this._renderClones(); }, _makePortal: function _makePortal() { @@ -200,105 +69,109 @@ var Shuffle = _react2['default'].createClass({ this._portalNode.style.left = '0px'; this._portalNode.style.top = '0px'; this._portalNode.style.position = 'absolute'; - _reactDom2['default'].findDOMNode(this.refs.container).appendChild(this._portalNode); - }, - - _addTransitionEndEvent: function _addTransitionEndEvent() { - setTimeout(this._finishAnimation, this.props.duration); - }, - - _startAnimation: function _startAnimation(nextProps) { - var _this3 = this; - - if (this.state.animating) { - return; + if (this.container !== null) { + this.container.appendChild(this._portalNode); } - - var cloneProps = (0, _objectAssign2['default'])({}, nextProps, { - positions: this._getPositions(), - initial: this.props.initial, - fade: this.props.fade, - scale: this.props.scale, - duration: this.props.duration - }); - this._renderClones(cloneProps, function () { - _this3._addTransitionEndEvent(); - _this3.setState({ animating: true }); - }); }, - _renderClonesToNewPositions: function _renderClonesToNewPositions(props) { - var cloneProps = (0, _objectAssign2['default'])({}, props, { - positions: this._getPositions(), - initial: this.props.initial, - fade: this.props.fade, - scale: this.props.scale, - duration: this.props.duration - }); - this._renderClones(cloneProps); - }, - - _finishAnimation: function _finishAnimation() { - this.setState({ animating: false }); - }, - - _getPositions: function _getPositions() { - var _this4 = this; + _renderClones: function _renderClones() { + var _this = this; - var positions = {}; + var styles = []; + var defaultStyles = []; _react2['default'].Children.forEach(this.props.children, function (child) { var ref = child.key; - var node = _reactDom2['default'].findDOMNode(_this4.refs[ref]); + var node = _this._refs[ref]; var rect = node.getBoundingClientRect(); var computedStyle = getComputedStyle(node); var marginTop = parseInt(computedStyle.marginTop, 10); var marginLeft = parseInt(computedStyle.marginLeft, 10); - var position = { - top: node.offsetTop - marginTop, - left: node.offsetLeft - marginLeft, - width: rect.width, - height: rect.height, - position: 'absolute', - opacity: 1 - }; - positions[ref] = position; - }); - return positions; - }, - _renderClonesInitially: function _renderClonesInitially() { - var cloneProps = (0, _objectAssign2['default'])({}, this.props, { - positions: this._getPositions(), - initial: this.props.initial, - fade: this.props.fade, - scale: this.props.scale, - duration: this.props.duration + styles.push({ + key: child.key, + style: { + width: (0, _reactMotion.spring)(rect.width, _this.props.springConfig), + height: (0, _reactMotion.spring)(rect.height, _this.props.springConfig), + left: (0, _reactMotion.spring)(node.offsetLeft - marginLeft, _this.props.springConfig), + top: (0, _reactMotion.spring)(node.offsetTop - marginTop, _this.props.springConfig), + scale: _this.props.scale ? (0, _reactMotion.spring)(1) : 1, + opacity: _this.props.fade ? (0, _reactMotion.spring)(1) : 1 + }, + data: child + }); + defaultStyles.push({ + key: child.key, + style: { + width: rect.width, + height: rect.height, + left: node.offsetLeft - marginLeft, + top: node.offsetTop - marginTop, + scale: _this.props.scale ? 0 : 1, + opacity: _this.props.fade ? 0 : 1 + }, + data: child + }); }); - _reactDom2['default'].render(_react2['default'].createElement(Clones, cloneProps), this._portalNode); - this.setState({ ready: true }); - }, - _renderClones: function _renderClones(props, cb) { - _reactDom2['default'].render(_react2['default'].createElement(Clones, props), this._portalNode, cb); + _reactDom2['default'].unstable_renderSubtreeIntoContainer(this, _react2['default'].createElement( + _reactMotion.TransitionMotion, + { + willLeave: function (style) { + return _extends({}, style.style, { + opacity: (0, _reactMotion.spring)(0, _this.props.springConfig), + scale: (0, _reactMotion.spring)(0, _this.props.springConfig) }); + }, + willEnter: function (style) { + return _extends({}, (0, _reactMotionLibStripStyle2['default'])(style.style), { + opacity: 0, + scale: 0 + }); + }, + defaultStyles: this.props.initial ? defaultStyles : null, + styles: styles }, + function (interpolatedStyles) { + return _react2['default'].createElement( + 'div', + null, + interpolatedStyles.map(function (config) { + return _react2['default'].cloneElement(config.data, { + key: config.key, + style: { + position: 'absolute', + width: config.style.width, + height: config.style.height, + left: config.style.left, + top: config.style.top, + opacity: config.style.opacity, + transform: 'scale(' + config.style.scale + ')' + } + }); + }) + ); + } + ), this._portalNode); }, - _childrenWithRefs: function _childrenWithRefs() { + var _this2 = this; + return _react2['default'].Children.map(this.props.children, function (child) { - return _react2['default'].cloneElement(child, { ref: child.key }); + return _react2['default'].cloneElement(child, { ref: function ref(r) { + _this2._refs = _this2._refs || {}; + _this2._refs[child.key] = r; + } }); }); }, - render: function render() { - var showContainer = this.props.initial ? 0 : 1; - if (this.state.ready) { - showContainer = 0; - } + var _this3 = this; + return _react2['default'].createElement( 'div', - _extends({ ref: 'container', style: { position: 'relative' } }, this.props), + _extends({ ref: function (ref) { + return _this3.container = ref; + }, style: { position: 'relative' } }, this.props), _react2['default'].createElement( 'div', - { style: { opacity: showContainer } }, + { style: { opacity: 0 } }, this._childrenWithRefs() ) ); diff --git a/src/shuffle.js b/src/shuffle.js index b641090..dbf01b8 100644 --- a/src/shuffle.js +++ b/src/shuffle.js @@ -35,7 +35,9 @@ const Shuffle = React.createClass({ }, componentWillUnmount() { - ReactDom.findDOMNode(this.refs.container).removeChild(this._portalNode); + if (this.container !== null){ + this.container.removeChild(this._portalNode); + } window.removeEventListener('resize', this._renderClones); }, @@ -48,7 +50,9 @@ const Shuffle = React.createClass({ this._portalNode.style.left = '0px'; this._portalNode.style.top = '0px'; this._portalNode.style.position = 'absolute'; - ReactDom.findDOMNode(this.refs.container).appendChild(this._portalNode); + if (this.container !== null){ + this.container.appendChild(this._portalNode); + } }, _renderClones() { @@ -56,7 +60,7 @@ const Shuffle = React.createClass({ let defaultStyles = [] React.Children.forEach(this.props.children, child => { let ref = child.key; - let node = ReactDom.findDOMNode(this.refs[ref]); + let node = this._refs[ref]; let rect = node.getBoundingClientRect(); let computedStyle = getComputedStyle(node); let marginTop = parseInt(computedStyle.marginTop, 10); @@ -88,7 +92,7 @@ const Shuffle = React.createClass({ }) }) - ReactDom.render(( + ReactDom.unstable_renderSubtreeIntoContainer(this,( ({ ...style.style, @@ -125,12 +129,15 @@ const Shuffle = React.createClass({ }, _childrenWithRefs() { return React.Children.map(this.props.children, (child) => - React.cloneElement(child, {ref: child.key}) + React.cloneElement(child,{ref: (r) => { + this._refs = this._refs || {} + this._refs[child.key] = r + }}) ); }, render() { return ( -
+
this.container = ref} style={{position: 'relative'}} {...this.props}>
{this._childrenWithRefs()}