Created
February 5, 2014 02:47
A React component based on SimpleScroller from react-touch. This component has support for detecting when the scrollable content area has changed size, and will reconfigure the Scroller. The project I'm working on scrolls through a list of captioned images, and the SimpleScroller component would calculate the scrollable dimensions before all the…
This file contains hidden or 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 */ | |
var React = require('react'); | |
var ZyngaScroller = require('react-touch/lib/environment/ZyngaScroller'); | |
var AnimatableContainer = require('react-touch/lib/primitives/AnimatableContainer'); | |
var TouchableArea = require('react-touch/lib/primitives/TouchableArea'); | |
var ANIMATABLE_CONTAINER_STYLE = { | |
bottom: 0, | |
left: 0, | |
position: 'absolute', | |
right: 0, | |
top: 0 | |
}; | |
/** | |
SimpleScroller component from react-touch, with event for detecting content resizes with overflow/underflow events. | |
See these articles: | |
http://www.backalleycoder.com/2013/03/14/oft-overlooked-overflow-and-underflow-events/ | |
http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/ | |
**/ | |
var DynamicContentScroller = React.createClass({ | |
addFlowListener: function(element, type, fn) { | |
var flow = type == 'over'; | |
element.addEventListener('OverflowEvent' in window ? 'overflowchanged' : type + 'flow', function(e){ | |
if (e.type == (type + 'flow') || | |
((e.orient == 0 && e.horizontalOverflow == flow) || | |
(e.orient == 1 && e.verticalOverflow == flow) || | |
(e.orient == 2 && e.horizontalOverflow == flow && e.verticalOverflow == flow))) { | |
e.flow = type; | |
return fn.call(this, e); | |
} | |
}, false); | |
}, | |
fireEvent: function(element, type, data, options) { | |
var options = options || {}, | |
event = document.createEvent('Event'); | |
event.initEvent(type, 'bubbles' in options ? options.bubbles : true, 'cancelable' in options ? options.cancelable : true); | |
for (var z in data) event[z] = data[z]; | |
element.dispatchEvent(event); | |
}, | |
addResizeListener: function(element, fn){ | |
var resize = 'onresize' in element; | |
if (!resize && !element._resizeSensor) { | |
var sensor = element._resizeSensor = this.refs.sensor.getDOMNode(); | |
var x = 0, y = 0, | |
first = sensor.firstElementChild.firstChild, | |
last = sensor.lastElementChild.firstChild, | |
matchFlow = (function(event){ | |
var change = false, | |
width = element.offsetWidth; | |
if (x != width) { | |
first.style.width = width - 1 + 'px'; | |
last.style.width = width + 1 + 'px'; | |
change = true; | |
x = width; | |
} | |
var height = element.offsetHeight; | |
if (y != height) { | |
first.style.height = height - 1 + 'px'; | |
last.style.height = height + 1 + 'px'; | |
change = true; | |
y = height; | |
} | |
if (change && event.currentTarget != element) this.fireEvent(element, 'resize'); | |
}).bind(this); | |
if (getComputedStyle(element).position == 'static'){ | |
element.style.position = 'relative'; | |
element._resizeSensor._resetPosition = true; | |
} | |
this.addFlowListener(sensor, 'over', matchFlow); | |
this.addFlowListener(sensor, 'under', matchFlow); | |
this.addFlowListener(sensor.firstElementChild, 'over', matchFlow); | |
this.addFlowListener(sensor.lastElementChild, 'under', matchFlow); | |
matchFlow({}); | |
} | |
var events = element._flowEvents || (element._flowEvents = []); | |
if (events.indexOf(fn) == -1) events.push(fn); | |
if (!resize) element.addEventListener('resize', fn, false); | |
element.onresize = function(e){ | |
events.forEach(function(fn){ | |
fn.call(element, e); | |
}); | |
}; | |
}, | |
removeResizeListener: function(element, fn){ | |
var index = element._flowEvents.indexOf(fn); | |
if (index > -1) element._flowEvents.splice(index, 1); | |
if (!element._flowEvents.length) { | |
var sensor = element._resizeSensor; | |
if (sensor) { | |
if (sensor._resetPosition) element.style.position = 'static'; | |
} | |
if ('onresize' in element) element.onresize = null; | |
delete element._flowEvents; | |
} | |
element.removeEventListener('resize', fn); | |
}, | |
handleContentResize: function(e) { | |
if(this.configured) { | |
// re-calculate the dimensions of the Scroller | |
this.configured = false; | |
this.configure(); | |
} | |
}, | |
getInitialState: function() { | |
return {left: 0, top: 0}; | |
}, | |
componentWillMount: function() { | |
this.scroller = new Scroller(this.handleScroll, this.props.options); | |
this.configured = false; | |
}, | |
componentWillUnmount: function() { | |
// remove event listeners | |
var node = this.refs.content.getDOMNode(); | |
this.removeResizeListener(node, this.handleContentResize); | |
}, | |
componentDidMount: function() { | |
// attach event listeners | |
var node = this.refs.content.getDOMNode(); | |
this.addResizeListener(node, this.handleContentResize); | |
this.configure(); | |
}, | |
componentDidUpdate: function() { | |
this.configure(); | |
}, | |
configure: function() { | |
if (this.configured) { | |
return; | |
} | |
this.configured = true; | |
var node = this.refs.content.getDOMNode(); | |
this.scroller.setDimensions( | |
this.getDOMNode().clientWidth, | |
this.getDOMNode().clientHeight, | |
node.clientWidth, | |
node.clientHeight | |
); | |
}, | |
handleScroll: function(left, top) { | |
this.setState({ | |
left: left, | |
top: top | |
}); | |
}, | |
render: function() { | |
return this.transferPropsTo( | |
<TouchableArea scroller={this.scroller} style={{overflow: 'hidden'}}> | |
<AnimatableContainer | |
translate={{x: -1 * this.state.left, y: -1 * this.state.top}} | |
style={ANIMATABLE_CONTAINER_STYLE}> | |
<div ref="content">{this.props.children} | |
<div ref="sensor" className="resize-sensor"> | |
<div className="resize-overflow"><div></div></div> | |
<div className="resize-underflow"><div></div></div> | |
</div> | |
</div> | |
</AnimatableContainer> | |
</TouchableArea> | |
); | |
} | |
}); | |
module.exports = DynamicContentScroller; |
This file contains hidden or 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
.resize-sensor, .resize-sensor > div { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
overflow: hidden; | |
z-index: -1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment