@v4fire/client
Version:
V4Fire client core library
192 lines (157 loc) • 3.75 kB
text/typescript
/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/
import Async from 'core/async';
/**
* Class to create touch gestures
*/
export default class Gestures {
/** @see [[TouchGesturesCreateOptions]] */
readonly options: TouchGesturesCreateOptions;
/** @see [[Async]] */
readonly async: Async = new Async();
/**
* Styled element that represents a touch position
*/
readonly cursor: HTMLDivElement = document.createElement('div');
/**
* Steps to perform
*/
steps: Array<Required<TouchGesturePoint>> = [];
/**
* @param opts
*/
constructor(opts: TouchGesturesCreateOptions) {
this.options = opts;
Object.assign(this.cursor.style, {
height: '20px',
width: '20px',
backgroundColor: 'red',
borderRadius: '50%',
position: 'absolute',
display: 'none',
zIndex: '10000'
});
document.body.appendChild(this.cursor);
}
/**
* Performs a swipe gesture
*
* @param points
* @param [emitTouchEnd]
*/
async swipe(points: TouchGesturePoint[], emitTouchEnd: boolean = true): Promise<void> {
this.fillSteps(points);
this.cursor.style.display = 'block';
const
firstStep = this.steps.shift(),
lastStep = this.steps.pop();
if (!firstStep || !lastStep) {
return;
}
this.emit(firstStep, 'touchstart');
this.emit(firstStep, 'touchmove');
for (let i = 0; i < this.steps.length; i++) {
const
step = this.steps[i];
await this.async.sleep(step.pause);
this.emit(step, 'touchmove');
}
await this.async.sleep(lastStep.pause);
this.emit(lastStep, 'touchmove');
if (emitTouchEnd) {
this.emit(lastStep, 'touchend');
}
this.cursor.style.display = 'none';
}
/**
* Generates steps for a swipe method
*
* @param count
* @param initialX
* @param initialY
* @param xChangePerStep
* @param yChangePerStep
* @param [opts]
*/
buildSteps(
count: number,
initialX: number,
initialY: number,
xChangePerStep: number,
yChangePerStep: number,
opts: Partial<TouchGesturesCreateOptions> = {}
): TouchGesturePoint[] {
const res: TouchGesturePoint[] = [
{
x: initialX,
y: initialY
}
];
count--;
for (let i = 0; i < count; i++) {
const
prev = res[res.length - 1];
res.push({
x: prev.x + xChangePerStep,
y: prev.y + yChangePerStep,
...opts
});
}
return res;
}
/**
* Emits a touch event
*
* @param step
* @param type
*/
protected emit(step: Required<TouchGesturePoint>, type: 'touchstart' | 'touchmove' | 'touchend'): void {
const
{dispatchEl, targetEl, x, y} = step;
const
resolvedDispatchEl = dispatchEl instanceof Element ? dispatchEl : document.querySelector(dispatchEl),
resolvedTargetEl = targetEl instanceof Element ? targetEl : document.querySelector(targetEl);
const touchEvent = new TouchEvent(type, {
touches: [
new Touch({
identifier: Math.random(),
target: resolvedTargetEl!,
clientX: x,
clientY: y
})
]
});
Object.assign(this.cursor.style, {
left: x.px,
top: y.px
});
resolvedDispatchEl?.dispatchEvent(touchEvent);
}
/**
* Fills an array with steps of points
* @param points
*/
protected fillSteps(points: TouchGesturePoint[]): void {
this.steps = [];
points.forEach((point) => {
const
lastStackEl = <CanUndef<TouchGesturePoint>>this.steps[this.steps.length - 1],
options = {...this.options};
if (lastStackEl) {
Object.assign(options, Object.select(lastStackEl, ['dispatchEl', 'targetEl', 'pause']));
}
const newPoint = {
pause: 5,
...options,
...point
};
this.steps.push(newPoint);
});
}
}
globalThis._Gestures = Gestures;