smooth-scrollbar
Version:
Customize scrollbar in modern browsers with smooth scrolling experience.
174 lines (130 loc) • 3.36 kB
text/typescript
import { getPosition } from './get-position';
export class Tracker {
readonly velocityMultiplier = window.devicePixelRatio;
updateTime = Date.now();
delta = { x: 0, y: 0 };
velocity = { x: 0, y: 0 };
lastPosition = { x: 0, y: 0 };
constructor(touch: Touch) {
this.lastPosition = getPosition(touch);
}
update(touch: Touch) {
const {
velocity,
updateTime,
lastPosition,
} = this;
const now = Date.now();
const position = getPosition(touch);
const delta = {
x: -(position.x - lastPosition.x),
y: -(position.y - lastPosition.y),
};
const duration = (now - updateTime) || 16.7;
const vx = delta.x / duration * 16.7;
const vy = delta.y / duration * 16.7;
velocity.x = vx * this.velocityMultiplier;
velocity.y = vy * this.velocityMultiplier;
this.delta = delta;
this.updateTime = now;
this.lastPosition = position;
}
}
export class TouchRecord {
private _activeTouchID: number;
private _touchList: { [id: number]: Tracker } = {};
private get _primitiveValue() {
return { x: 0, y: 0 };
}
isActive() {
return this._activeTouchID !== undefined;
}
getDelta() {
const tracker = this._getActiveTracker();
if (!tracker) {
return this._primitiveValue;
}
return { ...tracker.delta };
}
getVelocity() {
const tracker = this._getActiveTracker();
if (!tracker) {
return this._primitiveValue;
}
return { ...tracker.velocity };
}
getEasingDistance(damping: number) {
const deAcceleration = 1 - damping;
let distance = {
x: 0,
y: 0,
};
const vel = this.getVelocity();
Object.keys(vel).forEach(dir => {
// ignore small velocity
let v = Math.abs(vel[dir]) <= 10 ? 0 : vel[dir];
while (v !== 0) {
distance[dir] += v;
v = (v * deAcceleration) | 0;
}
});
return distance;
}
track(evt: TouchEvent) {
const {
targetTouches,
} = evt;
Array.from(targetTouches).forEach(touch => {
this._add(touch);
});
return this._touchList;
}
update(evt: TouchEvent) {
const {
touches,
changedTouches,
} = evt;
Array.from(touches).forEach(touch => {
this._renew(touch);
});
this._setActiveID(changedTouches);
return this._touchList;
}
release(evt: TouchEvent) {
delete this._activeTouchID;
Array.from(evt.changedTouches).forEach(touch => {
this._delete(touch);
});
}
private _add(touch: Touch) {
if (this._has(touch)) {
// reset tracker
this._delete(touch);
}
const tracker = new Tracker(touch);
this._touchList[touch.identifier] = tracker;
}
private _renew(touch: Touch) {
if (!this._has(touch)) {
return;
}
const tracker = this._touchList[touch.identifier];
tracker.update(touch);
}
private _delete(touch: Touch) {
delete this._touchList[touch.identifier];
}
private _has(touch: Touch): boolean {
return this._touchList.hasOwnProperty(touch.identifier);
}
private _setActiveID(touches: TouchList) {
this._activeTouchID = touches[touches.length - 1].identifier;
}
private _getActiveTracker(): Tracker {
const {
_touchList,
_activeTouchID,
} = this;
return _touchList[_activeTouchID];
}
}