Skip to content

Instantly share code, notes, and snippets.

@claus
Created September 9, 2016 20:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save claus/05f62a2d292bbd1dc7226a5fa748fe90 to your computer and use it in GitHub Desktop.
Save claus/05f62a2d292bbd1dc7226a5fa748fe90 to your computer and use it in GitHub Desktop.
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