Created
May 13, 2014 18:02
-
-
Save vegetableman/e9ed4df769c49343af84 to your computer and use it in GitHub Desktop.
Scroll Cloaking on reactjs
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
/** | |
* @jsx React.DOM | |
*/ | |
// Parent | |
var React = require('react'), | |
BlockItemView = require('./BlockItemView.jsx'); | |
module.exports = BlockView = React.createClass({displayName: 'BlockView', | |
debounce: function(func, wait) { | |
var timeout; | |
return function() { | |
var later = function() { | |
timeout = null; | |
func(); | |
}; | |
clearTimeout(timeout); | |
timeout = setTimeout(later, wait); | |
}; | |
}, | |
findTopView: function(childViews, viewportTop, min, max) { | |
if (max < min) { return min; } | |
var mid = Math.floor((min + max) / 2), | |
view = childViews[mid], | |
viewBottom = view.props.top + view.props.height; | |
if (viewBottom > viewportTop) { | |
return this.findTopView(childViews, viewportTop, min, mid-1); | |
} else { | |
return this.findTopView(childViews, viewportTop, mid+1, max); | |
} | |
}, | |
cloaker: function(childViews) { | |
if(!childViews || !childViews.length) return; | |
var that = this; | |
var windowTop = that.windowTop = that.w.scrollY; | |
var viewportTop = windowTop, | |
windowHeight = that.windowHeight, | |
windowBottom = windowTop + windowHeight, | |
viewportBottom = windowBottom + windowHeight, | |
topView = that.findTopView(childViews, viewportTop, 0, childViews.length-1), | |
toUnCloak = [], | |
toCloak = []; | |
var bottomView = topView; | |
// Find what's the bottomView | |
while (bottomView < childViews.length) { | |
var view = childViews[bottomView], | |
viewTop = view.props.top, | |
viewBottom = viewTop + view.props.height; | |
//break on reaching the bottomView | |
if (viewTop > viewportBottom) | |
break; | |
toUnCloak.push(childViews[bottomView]); | |
bottomView++; | |
} | |
if (bottomView >= childViews.length) { bottomView = childViews.length - 1; } | |
toCloak = childViews.slice(0, topView).concat(childViews.slice(bottomView+1, childViews.length)); | |
for(var j=0, tUCL = toUnCloak.length; j < tUCL; j++) { | |
var view = toUnCloak[j]; | |
if(view && view.state.cloaked) | |
BlockItemView.unCloak(view); | |
} | |
for(var i=0, tCL = toCloak.length; i < tCL; i++) { | |
var view = toCloak[i]; | |
if(view && !view.state.cloaked) | |
BlockItemView.cloak(view); | |
} | |
return bottomView; | |
}, | |
loadMore: function(rows, limit, size, totalPages) { | |
var childViews, | |
bottomView = this.cloaker(childViews = rows); | |
if(bottomView === childViews.length - 1) { | |
var props = childViews[bottomView].props; | |
if(this.windowTop + this.windowHeight > props.top + props.height) { | |
this.renderRows(); | |
} | |
} | |
}, | |
renderRows: function() { | |
var that = this, | |
rows = [], | |
collection = that.props.collection, | |
limit = that.props.limit; | |
that.pageNumber++; | |
for(var i = Math.max(0, (that.pageNumber - 1) * limit), pL = Math.min((that.pageNumber * limit), collection.length); i < pL; i++) { | |
rows.push(<BlockItemView item={collection[i]} pageNumber={that.pageNumber} key={"item-"+collection[i].lindex}></BlockItemView>); | |
} | |
that.setState({ | |
rows: that.state.rows.concat(rows), | |
limit: limit | |
}); | |
}, | |
getInitialState: function() { | |
var that = this; | |
that.pageNumber = 1; | |
return { | |
rows: (function(that) { | |
var rows = [], | |
collection = that.props.collection, | |
limit = that.props.limit; | |
for(var i = Math.max(0, (that.pageNumber - 1) * limit), pL = Math.min((that.pageNumber * limit), collection.length); i < pL; i++) { | |
rows.push(<BlockItemView item={collection[i]} key={"item-"+collection[i].lindex}></BlockItemView>); | |
} | |
return rows; | |
}(that)), | |
limit: that.props.limit | |
}; | |
}, | |
shouldComponentUpdate: function(nextProps, nextState) { | |
var propsChanged = this.props !== nextProps; | |
var stateChanged = this.state !== nextState; | |
return propsChanged || stateChanged; | |
}, | |
componentDidMount: function() { | |
var that = this; | |
that.w = window; | |
that.attachScroll(); | |
}, | |
attachScroll: function() { | |
var that = this; | |
that.windowHeight = that.w.innerHeight; | |
that.w.addEventListener('scroll', that.debounce(function() { | |
that.loadMore(that.state.rows, that.state.limit); | |
}, 100)); | |
}, | |
render: function() { | |
return <div>{this.state.rows}</div>; | |
} | |
}); | |
/** | |
* @jsx React.DOM | |
*/ | |
// Child | |
var React = require('react'); | |
module.exports = BlockItemView = React.createClass({displayName: 'BlockItemView', | |
statics: { | |
cloak: function(view) { | |
if(!view.state.cloaked && view.isMounted()) { | |
view.setState({cloaked: true}); | |
} | |
}, | |
unCloak: function(view) { | |
if(view.state.cloaked && view.isMounted()) { | |
view.setState({cloaked: false}); | |
} | |
} | |
}, | |
getInitialState: function() { | |
return {cloaked: false}; | |
}, | |
shouldComponentUpdate: function(nextProps, nextState) { | |
return this.state.cloaked !== nextState.cloaked; | |
}, | |
componentDidMount: function() { | |
var el = this.getDOMNode(); | |
if(el) { | |
var height = el.offsetHeight; | |
//cache height to avoid recalculation of height | |
if(!this.props.top) | |
this.props.top = el.offsetTop; | |
if(!this.props.height) | |
this.props.height = height; | |
el.style.minHeight = this.props.height + 'px'; | |
} | |
}, | |
render: function() { | |
if(his.state.cloaked) { | |
return <div style={{minHeight: this.props.height + 'px'}}> | |
//stuff | |
</div> | |
} | |
return ( | |
<div key={this.props.key}> | |
//stuff | |
</div>); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment