Skip to content

Instantly share code, notes, and snippets.

@vegetableman
Created May 13, 2014 18:02
Show Gist options
  • Save vegetableman/e9ed4df769c49343af84 to your computer and use it in GitHub Desktop.
Save vegetableman/e9ed4df769c49343af84 to your computer and use it in GitHub Desktop.
Scroll Cloaking on reactjs
/**
* @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