Created
September 9, 2016 20:47
-
-
Save claus/05f62a2d292bbd1dc7226a5fa748fe90 to your computer and use it in GitHub Desktop.
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
import _ from 'lodash'; | |
import React from 'react'; | |
function proxyToParent(ctx, fn, eventName) { | |
if (_.isFunction(ctx.props[eventName])) { | |
let parentFn = ctx.props[eventName]; | |
return _.wrap(fn, function (func, e) { | |
fn(e); | |
parentFn(e); | |
}); | |
} | |
return fn; | |
} | |
function sign(num) { | |
return (num < 0) ? -1 : 1; | |
} | |
let timeoutId; | |
let data = []; | |
let direction = 0; | |
let inertia = false; | |
let lastTouchCoord = null; | |
let touchStartCoord = null; | |
class Wheeler extends React.Component { | |
constructor(props, context) { | |
super(props, context); | |
this.onWheel = this.onWheel.bind(this); | |
this.onWheelTimeout = this.onWheelTimeout.bind(this); | |
} | |
isInertia(dy) { | |
data.push(dy); | |
let result = false; | |
let len = data.length; | |
if (len === 1) { | |
direction = sign(dy); | |
} else { | |
let curDirection = sign(dy); | |
if (direction !== curDirection) { | |
// change of swipe direction | |
data = [dy]; | |
direction = curDirection; | |
} else { | |
let sampleSize = this.props.sampleSize; | |
if (len > sampleSize) { | |
let signCount = 0; | |
let equalCount = 0; | |
for (let i = len - sampleSize; i < len; i++) { | |
let dyPrev = data[i - 1]; | |
let dyCur = data[i]; | |
let ddy = dyCur - dyPrev; | |
if (ddy === 0) { | |
// weed out mouse wheels which always emit the same high delta (usually >= 100) | |
if (Math.abs(dyPrev) > 10 && Math.abs(dyCur) > 10) { | |
equalCount++; | |
} | |
} else if (sign(ddy) === direction) { | |
// when actively swiping, the signs of the first dy and subsequent ddys tend to be the same (accelerate). | |
// when inertia kicks in, the signs differ (decelerate). | |
signCount++; | |
} | |
} | |
// report inertia, when out of the latest [sampleSize] events | |
// - less than [sampleSize / 2] accelerated (most decelerated) | |
// - all showed some de-/acceleration for higher deltas | |
result = (signCount < Math.round(sampleSize / 2) && equalCount !== sampleSize); | |
} | |
} | |
} | |
return result; | |
} | |
onWheelTimeout() { | |
this.props.onIdle(data.concat()); | |
inertia = false; | |
data = []; | |
} | |
onWheel(event) { | |
// from React renderers/dom/client/syntheticEvents/SyntheticWheelEvent.js: | |
// > Browsers without "deltaMode" is reporting in raw wheel delta where one | |
// > notch on the scroll is always +/- 120, roughly equivalent to pixels. | |
// > A good approximation of DOM_DELTA_LINE (1) is 5% of viewport size or | |
// > ~40 pixels, for DOM_DELTA_SCREEN (2) it is 87.5% of viewport size. | |
let dy = event.deltaY; | |
if (event.deltaMode === 1) { | |
dy *= window.innerHeight * 0.05; | |
} else if (event.deltaMode === 2) { | |
dy *= window.innerHeight * 0.875; | |
} | |
if (!this.isInertia(dy)) { | |
inertia = false; | |
this.props.onWheel(event); | |
} else if (!inertia) { | |
inertia = true; | |
this.props.onInertia(data.concat()); | |
} | |
clearTimeout(timeoutId); | |
timeoutId = setTimeout(this.onWheelTimeout, 100); | |
} | |
render() { | |
let children = this.props.children; | |
let events = {}; | |
if (this.props.disable !== true) { | |
events = { | |
onWheel: this.onWheel, | |
}; | |
// Make sure we keep parent events; | |
_.forOwn(events, function (value, key) { | |
events[key] = proxyToParent(children, value, key); | |
}); | |
} | |
return React.cloneElement(children, events); | |
} | |
} | |
Wheeler.defaultProps = { | |
sampleSize: 8, | |
onInertia: function () {}, | |
onIdle: function () {} | |
}; | |
Wheeler.propTypes = { | |
children: React.PropTypes.element.isRequired, | |
onWheel: React.PropTypes.func.isRequired, | |
onInertia: React.PropTypes.func, | |
onIdle: React.PropTypes.func, | |
sampleSize: React.PropTypes.number, | |
disable: React.PropTypes.bool | |
}; | |
export default Wheeler; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment