tweb/src/helpers/lethargy.ts

100 lines
3.3 KiB
TypeScript

/**
* Lethargy help distinguish between scroll events initiated by the user, and those by inertial scrolling.
* Lethargy does not have external dependencies.
*
* @param stability - Specifies the length of the rolling average.
* In effect, the larger the value, the smoother the curve will be.
* This attempts to prevent anomalies from firing 'real' events. Valid values are all positive integers,
* but in most cases, you would need to stay between 5 and around 30.
*
* @param sensitivity - Specifies the minimum value for wheelDelta for it to register as a valid scroll event.
* Because the tail of the curve have low wheelDelta values,
* this will stop them from registering as valid scroll events.
* The unofficial standard wheelDelta is 120, so valid values are positive integers below 120.
*
* @param tolerance - Prevent small fluctuations from affecting results.
* Valid values are decimals from 0, but should ideally be between 0.05 and 0.3.
*
* Based on https://github.com/d4nyll/lethargy
*/
export type LethargyConfig = {
stability?: number;
sensitivity?: number;
tolerance?: number;
delay?: number;
};
export class Lethargy {
private stability: number;
private sensitivity: number;
private tolerance: number;
private delay: number;
private lastUpDeltas: Array<number>;
private lastDownDeltas: Array<number>;
private deltasTimestamp: Array<number>;
constructor({
stability = 8,
sensitivity = 100,
tolerance = 1.1,
delay = 150
}: LethargyConfig = {}) {
this.stability = stability;
this.sensitivity = sensitivity;
this.tolerance = tolerance;
this.delay = delay;
this.lastUpDeltas = new Array(this.stability * 2).fill(0);
this.lastDownDeltas = new Array(this.stability * 2).fill(0);
this.deltasTimestamp = new Array(this.stability * 2).fill(0);
}
check(e: any) {
let lastDelta;
e = e.originalEvent || e;
if(e.wheelDelta !== undefined) {
lastDelta = e.wheelDelta;
} else if(e.deltaY !== undefined) {
lastDelta = e.deltaY * -40;
} else if(e.detail !== undefined || e.detail === 0) {
lastDelta = e.detail * -40;
}
this.deltasTimestamp.push(Date.now());
this.deltasTimestamp.shift();
if(lastDelta > 0) {
this.lastUpDeltas.push(lastDelta);
this.lastUpDeltas.shift();
return this.isInertia(1);
} else {
this.lastDownDeltas.push(lastDelta);
this.lastDownDeltas.shift();
return this.isInertia(-1);
}
}
isInertia(direction: number) {
const lastDeltas = direction === -1 ? this.lastDownDeltas : this.lastUpDeltas;
if(lastDeltas[0] === undefined) return direction;
if(
this.deltasTimestamp[this.stability * 2 - 2] + this.delay > Date.now() &&
lastDeltas[0] === lastDeltas[this.stability * 2 - 1]
) {
return false;
}
const lastDeltasOld = lastDeltas.slice(0, this.stability);
const lastDeltasNew = lastDeltas.slice(this.stability, this.stability * 2);
const oldSum = lastDeltasOld.reduce((t, s) => t + s);
const newSum = lastDeltasNew.reduce((t, s) => t + s);
const oldAverage = oldSum / lastDeltasOld.length;
const newAverage = newSum / lastDeltasNew.length;
return Math.abs(oldAverage) < Math.abs(newAverage * this.tolerance) &&
this.sensitivity < Math.abs(newAverage);
}
}