Skip to content

Instantly share code, notes, and snippets.

@sebringj
Created May 3, 2016 14:26
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 sebringj/a38364ae9ab4fbd9e871eef87db624a2 to your computer and use it in GitHub Desktop.
Save sebringj/a38364ae9ab4fbd9e871eef87db624a2 to your computer and use it in GitHub Desktop.
'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