Skip to content

Instantly share code, notes, and snippets.

@jlogsdon
Created September 23, 2015 17:28
Show Gist options
  • Save jlogsdon/f0f1460f653f9fc00336 to your computer and use it in GitHub Desktop.
Save jlogsdon/f0f1460f653f9fc00336 to your computer and use it in GitHub Desktop.
import React, { Component, PropTypes, cloneElement } from 'react';
import { debounce } from '../utils';
function elementSize(el) {
const bounds = el.getBoundingClientRect();
const styles = window.getComputedStyle(el);
const getAndParse = (prop) => parseFloat(styles.getPropertyValue(prop)) || 0;
const height = (bounds.height|0) + getAndParse('margin-top') + getAndParse('margin-bottom');
const width = (bounds.width|0) + getAndParse('margin-left') + getAndParse('margin-right');
return [width, height];
}
function getHeight(numChildren, childHeight, maxHeight) {
const fullHeight = numChildren * childHeight;
return Math.min(fullHeight, maxHeight);
}
function getElementHeight(el) {
const marginTop = parseInt(window.getComputedStyle(el).marginTop);
return elementSize(el)[1] - marginTop;
}
export default class LazyRender extends Component {
static propTypes = {
children: PropTypes.array.isRequired,
maxHeight: PropTypes.number.isRequired,
className: PropTypes.string,
itemPadding: PropTypes.number
}
static defaultProps = {
itemPadding: 26
}
constructor(props) {
super(props);
this.state = {
childrenTop: 0,
childrenToRender: 10,
scrollTop: 0,
height: this.props.maxHeight
};
}
onScroll() {
const container = this.refs.container;
const scrollTop = container.scrollTop;
const childrenTop = Math.floor(scrollTop / this.state.childHeight);
const childrenBottom = Math.max(0,
(this.props.children.length - childrenTop - this.state.childrenToRender));
this.setState({ childrenTop, childrenBottom, scrollTop });
}
componentWillReceiveProps(nextProps) {
const childrenTop = Math.floor(this.state.scrollTop / this.state.childHeight);
const childrenBottom = Math.max(0,
(nextProps.children.length - childrenTop - this.state.childrenToRender));
const height = getHeight(
nextProps.children.length,
this.state.childHeight,
nextProps.maxHeight
);
let numberOfItems = Math.ceil(height / this.state.childHeight);
if (height === this.props.maxHeight) {
numberOfItems += this.props.itemPadding;
}
this.setState({
childrenTop, childrenBottom, height,
childrenToRender: numberOfItems
});
}
componentDidMount() {
const childHeight = this.getChildHeight();
const height = getHeight(
this.props.children.length,
childHeight,
this.props.maxHeight
);
let numberOfItems = Math.ceil(height / childHeight);
if (height === this.props.maxHeight) {
numberOfItems += this.props.itemPadding;
}
this.setState({
childHeight, height,
childrenToRender: numberOfItems,
childrenTop: 0,
childrenBottom: this.props.children.length
});
}
componentDidUpdate() {
if (this.state.childHeight !== this.getChildHeight()) {
this.setState({
childHeight: this.getChildHeight()
});
}
}
getChildHeight() {
const el = this.refs['child-0'];
return getElementHeight(el);
}
render() {
const { childrenBottom, childrenTop } = this.state;
const childrenToRender = this.props.children.slice(childrenTop, childrenTop + this.state.childrenToRender);
const children = childrenToRender.map((child, i) => {
if (i === 0) {
return cloneElement(child, {
ref: 'child-0',
key: 0
});
}
return child;
});
const stylePadTop = {height: this.state.childHeight * childrenTop};
const stylePadBot = {height: this.state.childHeight * childrenBottom};
const style = {height: this.state.height, overflowY: 'auto'};
children.unshift(<div style={stylePadTop} key="top"/>);
children.push(<div style={stylePadBot} key="bottom"/>);
return (
<div
style={style}
className={this.props.className}
onScroll={debounce(::this.onScroll, 10)}
ref="container">
{children}
</div>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment