aboutsummaryrefslogtreecommitdiff
path: root/ui/static/js/touch_handler.js
diff options
context:
space:
mode:
authorGravatar dzaikos <dzaikos@users.noreply.github.com> 2022-09-30 05:37:57 +0000
committerGravatar Frédéric Guillot <f@miniflux.net> 2023-03-28 18:00:57 -0700
commit7d252ea45b4d00e9adb29528954c0589ab274a62 (patch)
tree9548d96772c56b5769bb26d1e83e8eccaa199a3b /ui/static/js/touch_handler.js
parent140a40acaf4eeaad4a61fa1e9aa4438e6085f318 (diff)
downloadv2-7d252ea45b4d00e9adb29528954c0589ab274a62.tar.gz
v2-7d252ea45b4d00e9adb29528954c0589ab274a62.tar.zst
v2-7d252ea45b4d00e9adb29528954c0589ab274a62.zip
Add swipe as option for gesture navigation between entries.
* Refactor `TouchHandler` to handle double-tap and swipe gestures. * Renamed existing `onTouch` JavaScript methods to `onItemTouch` and added `onContentTouch` methods for swipe gesture. * Refactor double-tap. It's now a method in `TouchHandler` versus anonymous functions in `listen()` method. * Updated CSS classes. * Added `touch-action` CSS for `.entry-content`. * Renamed CSS classes for adding events in `TouchHandler`. * Updated users settings to replace checkbox for double tap with select for none, double tap, or swipe. * Added database migrations for new gesture_nav option. * Rename `users.double_tap` to `users.gesture_nav` and migrate existing user settings. * Updated translation files. (Non-English updated with Google Translate.) Resolves #1449, closes #1495
Diffstat (limited to 'ui/static/js/touch_handler.js')
-rw-r--r--ui/static/js/touch_handler.js127
1 files changed, 88 insertions, 39 deletions
diff --git a/ui/static/js/touch_handler.js b/ui/static/js/touch_handler.js
index e06d81aa..99c1d5b2 100644
--- a/ui/static/js/touch_handler.js
+++ b/ui/static/js/touch_handler.js
@@ -8,6 +8,7 @@ class TouchHandler {
start: { x: -1, y: -1 },
move: { x: -1, y: -1 },
moved: false,
+ time: 0,
element: null
};
}
@@ -33,7 +34,7 @@ class TouchHandler {
return DomHelper.findParent(element, "entry-swipe");
}
- onTouchStart(event) {
+ onItemTouchStart(event) {
if (event.touches === undefined || event.touches.length !== 1) {
return;
}
@@ -45,7 +46,7 @@ class TouchHandler {
this.touch.element.style.transitionDuration = "0s";
}
- onTouchMove(event) {
+ onItemTouchMove(event) {
if (event.touches === undefined || event.touches.length !== 1 || this.element === null) {
return;
}
@@ -71,15 +72,15 @@ class TouchHandler {
}
}
- onTouchEnd(event) {
+ onItemTouchEnd(event) {
if (event.touches === undefined) {
return;
}
if (this.touch.element !== null) {
- let distance = Math.abs(this.calculateDistance());
+ let absDistance = Math.abs(this.calculateDistance());
- if (distance > 75) {
+ if (absDistance > 75) {
toggleEntryStatus(this.touch.element);
}
@@ -92,47 +93,95 @@ class TouchHandler {
this.reset();
}
+ onContentTouchStart(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.time = Date.now();
+ }
+
+ onContentTouchMove(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;
+ }
+
+ onContentTouchEnd(event) {
+ if (event.touches === undefined) {
+ return;
+ }
+
+ let distance = this.calculateDistance();
+ let absDistance = Math.abs(distance);
+ let now = Date.now();
+
+ if (now - this.touch.time <= 1000 && absDistance > 75) {
+ if (distance > 0) {
+ goToPage("previous");
+ } else {
+ goToPage("next");
+ }
+ }
+
+ this.reset();
+ }
+
+ onTapEnd(event) {
+ if (event.touches === undefined) {
+ return;
+ }
+
+ let now = Date.now();
+
+ if (this.touch.start.x !== -1 && now - this.touch.time <= 200) {
+ let innerWidthHalf = window.innerWidth / 2;
+
+ if (this.touch.start.x >= innerWidthHalf && event.changedTouches[0].clientX >= innerWidthHalf) {
+ goToPage("next");
+ } else if (this.touch.start.x < innerWidthHalf && event.changedTouches[0].clientX < innerWidthHalf) {
+ goToPage("previous");
+ }
+
+ this.reset();
+ } else {
+ this.reset();
+ this.touch.start.x = event.changedTouches[0].clientX;
+ this.touch.time = now;
+ }
+ }
+
listen() {
- let elements = document.querySelectorAll(".entry-swipe");
let hasPassiveOption = DomHelper.hasPassiveEventListenerOption();
+ let elements = document.querySelectorAll(".entry-swipe");
+
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("touchstart", (e) => this.onItemTouchStart(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchmove", (e) => this.onItemTouchMove(e), hasPassiveOption ? { passive: false } : false);
+ element.addEventListener("touchend", (e) => this.onItemTouchEnd(e), hasPassiveOption ? { passive: true } : false);
element.addEventListener("touchcancel", () => this.reset(), hasPassiveOption ? { passive: true } : false);
});
- let entryContentElement = document.querySelector(".entry-content");
- if (entryContentElement && entryContentElement.classList.contains('double-tap')) {
- 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);
- });
+ let element = document.querySelector(".entry-content");
+
+ if (element) {
+ if (element.classList.contains("gesture-nav-tap")) {
+ element.addEventListener("touchend", (e) => this.onTapEnd(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchmove", () => this.reset(), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchcancel", () => this.reset(), hasPassiveOption ? { passive: true } : false);
+ } else if (element.classList.contains("gesture-nav-swipe")) {
+ element.addEventListener("touchstart", (e) => this.onContentTouchStart(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchmove", (e) => this.onContentTouchMove(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchend", (e) => this.onContentTouchEnd(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchcancel", () => this.reset(), hasPassiveOption ? { passive: true } : false);
+ }
}
}
}