-
-
Save sylvesteraswin/84d5a7ace192ba875ba0d04e94009dac to your computer and use it in GitHub Desktop.
infinite scrolling pattern with react fiber (featuring intersection observers)
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
// inifinite scrolling of content without extra wrappers | |
const { render, findDOMNode } = ReactDOMFiber | |
class App extends React.Component { | |
render() { | |
// wrap the root element with an Intersection Observer, exposing .observe for children | |
return <Intersection> | |
<div style={{ height: 200, overflow: 'auto' }}> | |
<Page offset={0} count={10} /> | |
</div> | |
</Intersection> | |
} | |
} | |
class Page extends React.Component { | |
static contextTypes = { | |
observe: React.PropTypes.func | |
} | |
state = { | |
next: false | |
} | |
componentDidMount() { | |
// load more content when this element comes into view | |
this.context.observe(this.loadMore, () => this.setState({ next: true })) | |
} | |
render() { | |
let { count, offset } = this.props | |
return [ | |
times(count, i => | |
<div> | |
article {this.props.offset + i} | |
</div>), | |
this.state.next ? | |
<Page offset={offset + count} count={count}/> : | |
<div ref={x => this.loadMore = x || this.loadMore }> load more ...</div> | |
] | |
} | |
} | |
class Intersection extends React.Component { | |
static childContextTypes = { | |
observe: React.PropTypes.func | |
} | |
getChildContext(){ | |
return { | |
observe: this.observe | |
} | |
} | |
elements = new Map() | |
elementBuffer = [] | |
observe = (element, callback) => { | |
this.elements.set(element, callback) | |
// this funny bit to handle react's lifecycle order | |
if(!this.observer){ | |
this.elementBuffer.push(element) | |
} | |
else { | |
this.observer.observe(element) | |
} | |
} | |
onIntersect = (entries, observer) => { | |
entries.forEach(entry => this.elements.get(entry.target)(entry)) | |
} | |
componentDidMount(){ | |
this.observer = new IntersectionObserver(this.onIntersect, { | |
root: findDOMNode(this), | |
rootMargin: '0px', | |
threshold: 0.25 | |
}) | |
this.elementBuffer.forEach(element => this.observer.observe(element)) | |
this.elementBuffer = [] | |
} | |
render() { | |
return this.props.children | |
} | |
} | |
function times(n, fn){ | |
let arr = [] | |
for(let i=0; i< n; i++){ | |
arr.push(fn(i)) | |
} | |
return arr | |
} | |
render(<App/>, window.app) | |
// homework - | |
// reclaim memory by removing dom nodes from the top without jitter | |
// go both ways; start from the middle and scroll up | |
// cleanup element handlers after loading |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment