2023-05-23 19:50:14 +02:00
|
|
|
import { isAndroid } from "../utils/device.js";
|
|
|
|
import { matches } from "../utils/util.js";
|
2020-05-08 20:12:52 +02:00
|
|
|
|
2020-03-11 08:13:53 +01:00
|
|
|
const SWIPE_THRESHOLD = 40;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Controls all touch interactions and navigations for
|
|
|
|
* a presentation.
|
|
|
|
*/
|
|
|
|
export default class Touch {
|
2023-05-23 19:50:14 +02:00
|
|
|
constructor(Reveal) {
|
|
|
|
this.Reveal = Reveal;
|
|
|
|
|
|
|
|
// Holds information about the currently ongoing touch interaction
|
|
|
|
this.touchStartX = 0;
|
|
|
|
this.touchStartY = 0;
|
|
|
|
this.touchStartCount = 0;
|
|
|
|
this.touchCaptured = false;
|
|
|
|
|
|
|
|
this.onPointerDown = this.onPointerDown.bind(this);
|
|
|
|
this.onPointerMove = this.onPointerMove.bind(this);
|
|
|
|
this.onPointerUp = this.onPointerUp.bind(this);
|
|
|
|
this.onTouchStart = this.onTouchStart.bind(this);
|
|
|
|
this.onTouchMove = this.onTouchMove.bind(this);
|
|
|
|
this.onTouchEnd = this.onTouchEnd.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
bind() {
|
|
|
|
let revealElement = this.Reveal.getRevealElement();
|
|
|
|
|
|
|
|
if ("onpointerdown" in window) {
|
|
|
|
// Use W3C pointer events
|
|
|
|
revealElement.addEventListener("pointerdown", this.onPointerDown, false);
|
|
|
|
revealElement.addEventListener("pointermove", this.onPointerMove, false);
|
|
|
|
revealElement.addEventListener("pointerup", this.onPointerUp, false);
|
|
|
|
} else if (window.navigator.msPointerEnabled) {
|
|
|
|
// IE 10 uses prefixed version of pointer events
|
|
|
|
revealElement.addEventListener(
|
|
|
|
"MSPointerDown",
|
|
|
|
this.onPointerDown,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
revealElement.addEventListener(
|
|
|
|
"MSPointerMove",
|
|
|
|
this.onPointerMove,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
revealElement.addEventListener("MSPointerUp", this.onPointerUp, false);
|
|
|
|
} else {
|
|
|
|
// Fall back to touch events
|
|
|
|
revealElement.addEventListener("touchstart", this.onTouchStart, false);
|
|
|
|
revealElement.addEventListener("touchmove", this.onTouchMove, false);
|
|
|
|
revealElement.addEventListener("touchend", this.onTouchEnd, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
unbind() {
|
|
|
|
let revealElement = this.Reveal.getRevealElement();
|
|
|
|
|
|
|
|
revealElement.removeEventListener("pointerdown", this.onPointerDown, false);
|
|
|
|
revealElement.removeEventListener("pointermove", this.onPointerMove, false);
|
|
|
|
revealElement.removeEventListener("pointerup", this.onPointerUp, false);
|
|
|
|
|
|
|
|
revealElement.removeEventListener(
|
|
|
|
"MSPointerDown",
|
|
|
|
this.onPointerDown,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
revealElement.removeEventListener(
|
|
|
|
"MSPointerMove",
|
|
|
|
this.onPointerMove,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
revealElement.removeEventListener("MSPointerUp", this.onPointerUp, false);
|
|
|
|
|
|
|
|
revealElement.removeEventListener("touchstart", this.onTouchStart, false);
|
|
|
|
revealElement.removeEventListener("touchmove", this.onTouchMove, false);
|
|
|
|
revealElement.removeEventListener("touchend", this.onTouchEnd, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the target element prevents the triggering of
|
|
|
|
* swipe navigation.
|
|
|
|
*/
|
|
|
|
isSwipePrevented(target) {
|
|
|
|
// Prevent accidental swipes when scrubbing timelines
|
|
|
|
if (matches(target, "video, audio")) return true;
|
|
|
|
|
|
|
|
while (target && typeof target.hasAttribute === "function") {
|
|
|
|
if (target.hasAttribute("data-prevent-swipe")) return true;
|
|
|
|
target = target.parentNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler for the 'touchstart' event, enables support for
|
|
|
|
* swipe and pinch gestures.
|
|
|
|
*
|
|
|
|
* @param {object} event
|
|
|
|
*/
|
|
|
|
onTouchStart(event) {
|
|
|
|
if (this.isSwipePrevented(event.target)) return true;
|
|
|
|
|
|
|
|
this.touchStartX = event.touches[0].clientX;
|
|
|
|
this.touchStartY = event.touches[0].clientY;
|
|
|
|
this.touchStartCount = event.touches.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler for the 'touchmove' event.
|
|
|
|
*
|
|
|
|
* @param {object} event
|
|
|
|
*/
|
|
|
|
onTouchMove(event) {
|
|
|
|
if (this.isSwipePrevented(event.target)) return true;
|
|
|
|
|
|
|
|
let config = this.Reveal.getConfig();
|
|
|
|
|
|
|
|
// Each touch should only trigger one action
|
|
|
|
if (!this.touchCaptured) {
|
|
|
|
this.Reveal.onUserInput(event);
|
|
|
|
|
|
|
|
let currentX = event.touches[0].clientX;
|
|
|
|
let currentY = event.touches[0].clientY;
|
|
|
|
|
|
|
|
// There was only one touch point, look for a swipe
|
|
|
|
if (event.touches.length === 1 && this.touchStartCount !== 2) {
|
|
|
|
let availableRoutes = this.Reveal.availableRoutes({
|
|
|
|
includeFragments: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
let deltaX = currentX - this.touchStartX,
|
|
|
|
deltaY = currentY - this.touchStartY;
|
|
|
|
|
|
|
|
if (deltaX > SWIPE_THRESHOLD && Math.abs(deltaX) > Math.abs(deltaY)) {
|
|
|
|
this.touchCaptured = true;
|
|
|
|
if (config.navigationMode === "linear") {
|
|
|
|
if (config.rtl) {
|
|
|
|
this.Reveal.next();
|
|
|
|
} else {
|
|
|
|
this.Reveal.prev();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.Reveal.left();
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
deltaX < -SWIPE_THRESHOLD &&
|
|
|
|
Math.abs(deltaX) > Math.abs(deltaY)
|
|
|
|
) {
|
|
|
|
this.touchCaptured = true;
|
|
|
|
if (config.navigationMode === "linear") {
|
|
|
|
if (config.rtl) {
|
|
|
|
this.Reveal.prev();
|
|
|
|
} else {
|
|
|
|
this.Reveal.next();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.Reveal.right();
|
|
|
|
}
|
|
|
|
} else if (deltaY > SWIPE_THRESHOLD && availableRoutes.up) {
|
|
|
|
this.touchCaptured = true;
|
|
|
|
if (config.navigationMode === "linear") {
|
|
|
|
this.Reveal.prev();
|
|
|
|
} else {
|
|
|
|
this.Reveal.up();
|
|
|
|
}
|
|
|
|
} else if (deltaY < -SWIPE_THRESHOLD && availableRoutes.down) {
|
|
|
|
this.touchCaptured = true;
|
|
|
|
if (config.navigationMode === "linear") {
|
|
|
|
this.Reveal.next();
|
|
|
|
} else {
|
|
|
|
this.Reveal.down();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we're embedded, only block touch events if they have
|
|
|
|
// triggered an action
|
|
|
|
if (config.embedded) {
|
|
|
|
if (this.touchCaptured || this.Reveal.isVerticalSlide()) {
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Not embedded? Block them all to avoid needless tossing
|
|
|
|
// around of the viewport in iOS
|
|
|
|
else {
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// There's a bug with swiping on some Android devices unless
|
|
|
|
// the default action is always prevented
|
|
|
|
else if (isAndroid) {
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler for the 'touchend' event.
|
|
|
|
*
|
|
|
|
* @param {object} event
|
|
|
|
*/
|
|
|
|
onTouchEnd(event) {
|
|
|
|
this.touchCaptured = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert pointer down to touch start.
|
|
|
|
*
|
|
|
|
* @param {object} event
|
|
|
|
*/
|
|
|
|
onPointerDown(event) {
|
|
|
|
if (
|
|
|
|
event.pointerType === event.MSPOINTER_TYPE_TOUCH ||
|
|
|
|
event.pointerType === "touch"
|
|
|
|
) {
|
|
|
|
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
|
|
|
|
this.onTouchStart(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert pointer move to touch move.
|
|
|
|
*
|
|
|
|
* @param {object} event
|
|
|
|
*/
|
|
|
|
onPointerMove(event) {
|
|
|
|
if (
|
|
|
|
event.pointerType === event.MSPOINTER_TYPE_TOUCH ||
|
|
|
|
event.pointerType === "touch"
|
|
|
|
) {
|
|
|
|
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
|
|
|
|
this.onTouchMove(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert pointer up to touch end.
|
|
|
|
*
|
|
|
|
* @param {object} event
|
|
|
|
*/
|
|
|
|
onPointerUp(event) {
|
|
|
|
if (
|
|
|
|
event.pointerType === event.MSPOINTER_TYPE_TOUCH ||
|
|
|
|
event.pointerType === "touch"
|
|
|
|
) {
|
|
|
|
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
|
|
|
|
this.onTouchEnd(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|