cross-gesture
Version:
Gesture lib for the modern browsers
364 lines • 14.3 kB
JavaScript
import { getDirectionAndSpeed, pinchTimes, getRotateDeg } from './utils';
const MOVE_OUT_DISTANCE = 10;
export class CrossGesture {
constructor(el) {
// 配置
this.options = {
singleTapTimeout: 250,
longTapTimeout: 750,
};
// 事件监听回调函数
this.listenersMap = new Map();
// 之前滑动触点
this.touchPrePoints = {};
// 触摸点的滑动轨迹
this.touchPointsPath = new Map();
this.isMouseDown = false;
this.onWheel = (evt) => {
this.dispatch('wheel', evt);
};
this.onPointerDown = (evt) => {
this.dispatch('pointerdown', evt);
};
this.onPointerUp = (evt) => {
this.dispatch('pointerup', evt);
};
this.onPointerMove = (evt) => {
this.dispatch('pointermove', evt);
};
this.onMouseDown = (evt) => {
this.isMouseDown = true;
// dispatch mousedown
this.dispatch('mousedown', evt);
// dispatch press move
this.dispatchPressMove(evt);
};
this.onMouseMove = (evt) => {
// dispatch mousemove
this.dispatch('mousemove', evt);
if (!this.isMouseDown) {
return;
}
// dispatch press move
this.dispatchPressMove(evt);
};
this.onMouseUp = (evt) => {
this.isMouseDown = false;
// dispatch mouseup
this.dispatch('mouseup', evt);
};
this.onTouchStart = (evt) => {
// dispatch touchstart
this.dispatch('touchstart', evt);
// dispatch press move
this.dispatchPressMove(evt);
// update touch path
this.updatePathOfPoint(evt.touches);
// 清除上一个 single tap
if (this.singleTapTimer) {
clearTimeout(this.singleTapTimer);
this.singleTapTimer = undefined;
}
if (!this.longTapTimer) {
this.longTapPoint = evt.touches[0];
this.longTapTimer = setTimeout(() => {
// dispatch longTap
this.dispatch('longTap', evt);
}, this.options.longTapTimeout);
}
};
this.onTouchMove = (evt) => {
const touches = evt.touches;
if (touches.length > 1) {
evt.preventDefault();
}
// dispatch touchmove
this.dispatch('touchmove', evt);
// dispatch press move
this.dispatchPressMove(evt);
// pinch
const touchPrePoints = this.touchPrePoints.points || [];
if (touches.length > 1 && touchPrePoints && touchPrePoints.length > 1) {
const pre1 = touchPrePoints[0];
const pre2 = touchPrePoints[1];
const cur1 = touches[0];
const cur2 = touches[1];
const rect = this.element.getBoundingClientRect();
const fromPoints = [
{
x: pre1.clientX - rect.x,
y: pre1.clientY - rect.y,
},
{
x: pre2.clientX - rect.x,
y: pre2.clientY - rect.y,
},
];
const toPoints = [
{
x: cur1.clientX - rect.x,
y: cur1.clientY - rect.y,
},
{
x: cur2.clientX - rect.x,
y: cur2.clientY - rect.y,
},
];
const rotateDeg = getRotateDeg(fromPoints, toPoints);
const zoomCenter = {
x: (toPoints[0].x + toPoints[1].x) / 2,
y: (toPoints[0].y + toPoints[1].y) / 2,
};
const zoom = pinchTimes(fromPoints, toPoints);
this.dispatch('pinch', evt, {
fromPoints,
toPoints,
zoomCenter,
zoom,
});
this.dispatch('rotate', evt, {
fromPoints,
toPoints,
rotateCenter: zoomCenter,
rotateDeg,
});
this.dispatch('pinchAndRotate', evt, {
fromPoints,
toPoints,
rotateCenter: zoomCenter,
rotateDeg,
zoomCenter,
zoom,
});
}
// swipe
if (touchPrePoints.length > 0 && this.touchPrePoints.time) {
const preTouch = touchPrePoints[0];
const touch = touches[0];
const fromPoint = {
x: preTouch.clientX,
y: preTouch.clientY,
};
const toPoint = {
x: touch.clientX,
y: touch.clientY,
};
this.dispatch('swipe', evt, getDirectionAndSpeed(fromPoint, toPoint, Date.now() - this.touchPrePoints.time));
}
// update touch path
this.updatePathOfPoint(evt.touches);
for (let i = 0; i < evt.touches.length; i++) {
const touch = evt.touches[i];
// cancel longTap if move out of distance
if (this.longTapTimer &&
this.longTapPoint &&
this.longTapPoint.identifier === touch.identifier &&
this.moveOutDistance(touch.identifier)) {
clearTimeout(this.longTapTimer);
this.longTapTimer = undefined;
this.longTapPoint = undefined;
}
}
};
this.onTouchEnd = (evt) => {
// dispatch touchend
this.dispatch('touchend', evt);
// evt.changedTouches 为结束触摸的点
if (!evt.changedTouches || !evt.changedTouches.length) {
return;
}
for (let i = 0; i < evt.changedTouches.length; i++) {
const touchEnd = evt.changedTouches[i];
// 清除 long tap
if (this.longTapPoint && this.longTapPoint.identifier === touchEnd.identifier) {
this.longTapTimer && clearTimeout(this.longTapTimer);
this.longTapTimer = undefined;
this.longTapPoint = undefined;
}
// double tap
// singleTap event
if (evt.changedTouches.length === 1 && evt.touches.length === 0 && !this.moveOutDistance(touchEnd.identifier)) {
this.singleTapTimer = setTimeout(() => {
this.singleTapTimer = undefined;
this.dispatch('singleTap', evt);
}, this.options.singleTapTimeout);
}
const now = Date.now();
// 滑动轨迹
const path = this.touchPointsPath.get(touchEnd.identifier);
// tap event
// 没有超过长安时间
// 滑动没超过 10
if (path &&
path[0] &&
now - path[0].time < this.options.longTapTimeout &&
!this.moveOutDistance(touchEnd.identifier)) {
// dispatch tap event
this.dispatch('tap', evt);
// double tap event
if (this.lastTapTime && now - this.lastTapTime < this.options.singleTapTimeout) {
this.singleTapTimer && clearTimeout(this.singleTapTimer);
this.singleTapTimer = undefined;
this.lastTapTime = undefined;
this.dispatch('doubleTap', evt);
}
else {
this.lastTapTime = now;
}
}
// 清除滑动轨迹
this.touchPointsPath.delete(touchEnd.identifier);
}
// clear touch points
this.clearPrePoints(evt.changedTouches);
};
this.onTouchCancel = (evt) => {
var _a;
// dispatch touchcancel
this.dispatch('touchcancel', evt);
// cancel longTap
for (let i = 0; i < evt.touches.length; i++) {
const touch = evt.touches[i];
if (((_a = this.longTapPoint) === null || _a === void 0 ? void 0 : _a.identifier) === touch.identifier) {
this.longTapTimer && clearTimeout(this.longTapTimer);
this.longTapTimer = undefined;
this.longTapPoint = undefined;
this.touchPointsPath.delete(touch.identifier);
}
}
// clear touch points
this.clearPrePoints(evt.changedTouches);
};
const element = typeof el === 'string' ? document.querySelector(el) : el;
if (!element) {
throw new Error(`CrossGesture: Element ${el} does not exists on the document!`);
}
this.element = element;
this.element.addEventListener('touchstart', this.onTouchStart);
this.element.addEventListener('touchmove', this.onTouchMove, { passive: false });
this.element.addEventListener('touchend', this.onTouchEnd);
this.element.addEventListener('touchcancel', this.onTouchCancel);
this.element.addEventListener('touchstart', this.onTouchStart);
this.element.addEventListener('mousedown', this.onMouseDown);
this.element.addEventListener('mousemove', this.onMouseMove);
this.element.addEventListener('mouseup', this.onMouseUp);
this.element.addEventListener('pointerdown', this.onPointerDown);
this.element.addEventListener('pointerup', this.onPointerUp);
this.element.addEventListener('pointermove', this.onPointerMove);
this.element.addEventListener('wheel', this.onWheel);
}
updatePathOfPoint(touches) {
this.touchPrePoints = {
points: Array.from(touches),
time: Date.now(),
};
for (let i = 0; i < touches.length; i++) {
const touch = touches[i];
let path = this.touchPointsPath.get(touch.identifier);
if (!path) {
path = [];
this.touchPointsPath.set(touch.identifier, path);
}
path.push({
time: Date.now(),
pageX: touch.pageX,
pageY: touch.pageY,
});
}
}
dispatch(eventType, evt, detail) {
const listeners = this.listenersMap.get(eventType);
if (!listeners) {
return;
}
listeners.forEach((l) => {
l(evt, detail);
});
}
moveOutDistance(key, distance = MOVE_OUT_DISTANCE) {
const path = this.touchPointsPath.get(key);
if (!path) {
return false;
}
const firstPoint = path[0];
for (let i = 1; i < path.length; i++) {
const point = path[i];
if (point.pageX - firstPoint.pageX > distance || point.pageY - firstPoint.pageY > distance) {
return true;
}
}
return false;
}
clearPrePoints(touches) {
if (!this.touchPrePoints.points || !this.touchPrePoints.points.length) {
return;
}
const touchesMap = new Map();
for (let i = 0; i < touches.length; i++) {
const touch = touches[i];
touchesMap.set(touch.identifier, touch);
}
this.touchPrePoints.points = this.touchPrePoints.points.filter((p) => !touchesMap.has(p.identifier));
}
dispatchPressMove(evt) {
const touch = evt;
const rect = this.element.getBoundingClientRect();
if (touch.touches) {
if (touch.touches.length !== 1) {
return;
}
return this.dispatch('pressMove', touch, {
point: {
x: touch.touches[0].clientX - rect.x,
y: touch.touches[0].clientY - rect.y,
},
});
}
const mouse = evt;
return this.dispatch('pressMove', touch, {
point: {
x: mouse.clientX - rect.x,
y: mouse.clientY - rect.y,
},
});
}
addListener(eventType, listener) {
let listeners = this.listenersMap.get(eventType);
if (!listeners) {
listeners = [];
this.listenersMap.set(eventType, listeners);
}
listeners.push(listener);
}
removeListener(eventType, listener) {
const listeners = this.listenersMap.get(eventType);
if (!listeners) {
return;
}
// clear event timeout
if (eventType === 'singleTap') {
this.singleTapTimer && clearTimeout(this.singleTapTimer);
this.singleTapTimer = undefined;
}
else if (eventType === 'longTap') {
this.longTapTimer && clearTimeout(this.longTapTimer);
this.longTapTimer = undefined;
this.longTapPoint = undefined;
}
// clear listener
this.listenersMap.set(eventType, listeners.filter((cb) => cb !== listener));
}
destroy() {
this.singleTapTimer && clearTimeout(this.singleTapTimer);
this.longTapTimer && clearTimeout(this.longTapTimer);
this.singleTapTimer = undefined;
this.longTapTimer = undefined;
this.longTapPoint = undefined;
this.lastTapTime = undefined;
this.touchPrePoints.points = undefined;
this.touchPrePoints.time = undefined;
this.touchPointsPath.clear();
this.listenersMap.clear();
}
}
//# sourceMappingURL=index.js.map