miniflux/ui/static/js/touch_handler.js

133 lines
4.4 KiB
JavaScript

class TouchHandler {
constructor() {
this.reset();
}
reset() {
this.touch = {
start: { x: -1, y: -1 },
move: { x: -1, y: -1 },
element: null
};
}
calculateDistance() {
if (this.touch.start.x >= -1 && this.touch.move.x >= -1) {
let horizontalDistance = Math.abs(this.touch.move.x - this.touch.start.x);
let verticalDistance = Math.abs(this.touch.move.y - this.touch.start.y);
if (horizontalDistance > 30 && verticalDistance < 70) {
return this.touch.move.x - this.touch.start.x;
}
}
return 0;
}
findElement(element) {
if (element.classList.contains("touch-item")) {
return element;
}
return DomHelper.findParent(element, "touch-item");
}
onTouchStart(event) {
if (event.touches === undefined || event.touches.length !== 1) {
return;
}
this.reset();
this.touch.start.x = event.touches[0].clientX;
this.touch.start.y = event.touches[0].clientY;
this.touch.element = this.findElement(event.touches[0].target);
}
onTouchMove(event) {
if (event.touches === undefined || event.touches.length !== 1 || this.element === null) {
return;
}
this.touch.move.x = event.touches[0].clientX;
this.touch.move.y = event.touches[0].clientY;
let distance = this.calculateDistance();
let absDistance = Math.abs(distance);
if (absDistance > 0) {
let opacity = 1 - (absDistance > 75 ? 0.9 : absDistance / 75 * 0.9);
let tx = distance > 75 ? 75 : (distance < -75 ? -75 : distance);
this.touch.element.style.opacity = opacity;
this.touch.element.style.transform = "translateX(" + tx + "px)";
event.preventDefault();
}
}
onTouchEnd(event) {
if (event.touches === undefined) {
return;
}
if (this.touch.element !== null) {
let distance = Math.abs(this.calculateDistance());
if (distance > 75) {
toggleEntryStatus(this.touch.element);
}
// If not on the unread page, undo transform of the dragged element.
if (document.URL.split("/").indexOf("unread") == -1 || distance <= 75) {
this.touch.element.style.opacity = 1;
this.touch.element.style.transform = "none";
}
}
this.reset();
}
listen() {
let elements = document.querySelectorAll(".touch-item");
let hasPassiveOption = DomHelper.hasPassiveEventListenerOption();
elements.forEach((element) => {
element.addEventListener("touchstart", (e) => this.onTouchStart(e), hasPassiveOption ? { passive: true } : false);
element.addEventListener("touchmove", (e) => this.onTouchMove(e), hasPassiveOption ? { passive: false } : false);
element.addEventListener("touchend", (e) => this.onTouchEnd(e), hasPassiveOption ? { passive: true } : false);
element.addEventListener("touchcancel", () => this.reset(), hasPassiveOption ? { passive: true } : false);
});
let entryContentElement = document.querySelector(".entry-content");
if (entryContentElement) {
let doubleTapTimers = {
previous: null,
next: null
};
const detectDoubleTap = (doubleTapTimer, event) => {
const timer = doubleTapTimers[doubleTapTimer];
if (timer === null) {
doubleTapTimers[doubleTapTimer] = setTimeout(() => {
doubleTapTimers[doubleTapTimer] = null;
}, 200);
} else {
event.preventDefault();
goToPage(doubleTapTimer);
}
};
entryContentElement.addEventListener("touchend", (e) => {
if (e.changedTouches[0].clientX >= (entryContentElement.offsetWidth / 2)) {
detectDoubleTap("next", e);
} else {
detectDoubleTap("previous", e);
}
}, hasPassiveOption ? { passive: false } : false);
entryContentElement.addEventListener("touchmove", (e) => {
Object.keys(doubleTapTimers).forEach(timer => doubleTapTimers[timer] = null);
});
}
}
}