Created
May 3, 2016 14:26
-
-
Save sebringj/a38364ae9ab4fbd9e871eef87db624a2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var _ = require('lodash'); | |
var React = require('react'); | |
var ReactDOM = require('react-dom'); | |
var THROTTLE_TIME = 250; | |
var InfiniteChild = React.createClass({ | |
getInitialState: function() { | |
return { | |
visible: false, | |
style: { | |
position: 'absolute', | |
width: 0, | |
height: 0, | |
top: 0, | |
left: 0, | |
display: 'block', | |
overflow: 'hidden' | |
} | |
} | |
}, | |
setStyle: function(style) { | |
this.setState({ style: _.assign({}, this.state.style, style) }); | |
}, | |
getStyle: function() { | |
return this.state.style; | |
}, | |
setVisible: function(visible) { | |
this.setState({ visible: visible }); | |
}, | |
isVisible: function() { | |
return this.state.visible; | |
}, | |
render: function() { | |
if (this.state.visible) { | |
return ( | |
<div style={this.state.style}> | |
{this.props.children} | |
</div> | |
); | |
} else { | |
return null; | |
} | |
} | |
}); | |
var Infinite = React.createClass({ | |
displayName: "Infinite", | |
propTypes: { | |
itemWidth: React.PropTypes.number.isRequired, | |
itemHeight: React.PropTypes.number.isRequired | |
}, | |
getInitialState: function() { | |
return { | |
style: { | |
height: 0, | |
position: 'relative', | |
overflow: 'hidden' | |
}, | |
rows: 0, | |
columns: 0 | |
}; | |
}, | |
// RETURNS 0-based index from 1-based input indices!!!! | |
_getIndex: function(columns, r, c) { | |
return (r - 1) * columns + c - 1; | |
}, | |
_getChild: function(columns, r, c) { | |
return this.refs['child_' + this._getIndex(columns, r, c)]; | |
}, | |
_onResize: function() { | |
if (!this.refs.child_0) return; | |
// set parent container | |
var parentWidth = ReactDOM.findDOMNode(this.refs.parentElement).clientWidth; | |
var columns = Math.floor(parentWidth / this.props.itemWidth); | |
var childrenLength = this.props.children.length; | |
var rows = Math.ceil(childrenLength / columns); | |
// set position of children | |
var child; | |
for (var r = 1; r <= rows; r++) { | |
for (var c = 1; c <= columns && this._getIndex(columns, r, c) < childrenLength; c++) { | |
child = this._getChild(columns, r, c); | |
if (child) { | |
child.setStyle({ | |
width: this.props.itemWidth, | |
height: this.props.itemHeight, | |
left: (c - 1) * this.props.itemWidth, | |
top: (r - 1) * this.props.itemHeight | |
}); | |
} | |
} | |
} | |
this.setState({ | |
style: { | |
height: rows * this.props.itemHeight, | |
position: 'relative', | |
overflow: 'hidden' | |
}, | |
rows: rows, | |
columns: columns | |
}, this._onScroll.bind(this)); | |
}, | |
_onScroll: function() { | |
if (!this.refs.child_0) return; | |
var rows = this.state.rows; | |
var columns = this.state.columns; | |
var childrenLength = this.props.children.length; | |
var viewportTop = document.body.scrollTop; | |
var scrollingDown = true; | |
if (!this.scrollStart) this.scrollStart = viewportTop; | |
else { | |
scrollingDown = (viewportTop >= this.scrollStart); | |
this.scrollStart = viewportTop | |
} | |
var viewportBottom = viewportTop + window.innerHeight; | |
if (scrollingDown) viewportBottom += this.props.itemHeight; | |
else viewportTop -= this.props.itemHeight; | |
var parentOffset = ReactDOM.findDOMNode(this.refs.parentElement).getBoundingClientRect().top + viewportTop; | |
// determine which children are visible | |
var child, style, visible, top, bottom; | |
for (var r = 1; r <= rows; r++) { | |
for (var c = 1; c <= columns && this._getIndex(columns, r, c) < childrenLength; c++) { | |
child = this._getChild(columns, r, c); | |
if (child && c === 1) { | |
style = child.getStyle(); | |
top = style.top + parentOffset; | |
bottom = top + style.height; | |
visible = ((bottom > viewportTop && bottom < viewportBottom) || (top > viewportTop && top < viewportBottom)); | |
} | |
if (child) child.setVisible(visible); | |
} | |
} | |
}, | |
componentDidMount: function() { | |
if (!this._throttledOnResize) this._throttledOnResize = _.throttle(this._onResize.bind(this), THROTTLE_TIME); | |
if (!this._throttledOnScroll) this._throttledOnScroll = _.throttle(this._onScroll.bind(this), THROTTLE_TIME); | |
window.addEventListener('resize', this._throttledOnResize); | |
window.addEventListener('scroll', this._throttledOnScroll); | |
this._throttledOnResize(); | |
}, | |
componentDidUpdate: function() { | |
this._throttledOnResize(); | |
}, | |
componentWillUnmount: function() { | |
window.removeEventListener('resize', this._throttledOnResize); | |
window.removeEventListener('scroll', this._throttledOnScroll); | |
}, | |
render: function() { | |
if (!this.props.children || !this.props.children.length) return null; | |
return ( | |
<div ref="parentElement" className="infinite" style={this.state.style}> | |
{React.Children.map(this.props.children, function(child, index) { | |
return ( | |
<InfiniteChild key={index} ref={'child_' + index}>{child}</InfiniteChild> | |
); | |
})} | |
</div> | |
); | |
} | |
}); | |
module.exports = Infinite; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment