diff options
author | 2022-09-30 05:37:57 +0000 | |
---|---|---|
committer | 2023-03-28 18:00:57 -0700 | |
commit | 7d252ea45b4d00e9adb29528954c0589ab274a62 (patch) | |
tree | 9548d96772c56b5769bb26d1e83e8eccaa199a3b /ui/static/js/touch_handler.js | |
parent | 140a40acaf4eeaad4a61fa1e9aa4438e6085f318 (diff) | |
download | v2-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.js | 127 |
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); + } } } } |