From 74bb1ccf4c6f31ffdb7419d5a58a4f592aa6006b Mon Sep 17 00:00:00 2001 From: Vladimir Kharlampidi Date: Thu, 28 Sep 2023 13:53:43 +0300 Subject: [PATCH] feat: add handling for native touch events fixes #6478 fixes #6381 fixes #6897 --- src/core/core.mjs | 3 +- src/core/events/index.mjs | 4 +++ src/core/events/onTouchEnd.mjs | 26 ++++++++++----- src/core/events/onTouchMove.mjs | 22 ++++++++---- src/core/events/onTouchStart.mjs | 57 ++++++++++++++++++++++---------- 5 files changed, 78 insertions(+), 34 deletions(-) diff --git a/src/core/core.mjs b/src/core/core.mjs index e0ea3c424..824085df4 100644 --- a/src/core/core.mjs +++ b/src/core/core.mjs @@ -193,7 +193,8 @@ class Swiper { velocities: [], allowMomentumBounce: undefined, startMoving: undefined, - evCache: [], + pointerId: null, + touchId: null, }, // Clicks diff --git a/src/core/events/index.mjs b/src/core/events/index.mjs index 6076c1d47..7be218f6a 100644 --- a/src/core/events/index.mjs +++ b/src/core/events/index.mjs @@ -19,10 +19,14 @@ const events = (swiper, method) => { const swiperMethod = method; // Touch Events + el[domMethod]('touchstart', swiper.onTouchStart, { passive: false }); el[domMethod]('pointerdown', swiper.onTouchStart, { passive: false }); + document[domMethod]('touchmove', swiper.onTouchMove, { passive: false, capture }); document[domMethod]('pointermove', swiper.onTouchMove, { passive: false, capture }); + document[domMethod]('touchend', swiper.onTouchEnd, { passive: true }); document[domMethod]('pointerup', swiper.onTouchEnd, { passive: true }); document[domMethod]('pointercancel', swiper.onTouchEnd, { passive: true }); + document[domMethod]('touchcancel', swiper.onTouchEnd, { passive: true }); document[domMethod]('pointerout', swiper.onTouchEnd, { passive: true }); document[domMethod]('pointerleave', swiper.onTouchEnd, { passive: true }); document[domMethod]('contextmenu', swiper.onTouchEnd, { passive: true }); diff --git a/src/core/events/onTouchEnd.mjs b/src/core/events/onTouchEnd.mjs index 4a781977a..c036a8c51 100644 --- a/src/core/events/onTouchEnd.mjs +++ b/src/core/events/onTouchEnd.mjs @@ -3,13 +3,25 @@ import { now, nextTick } from '../../shared/utils.mjs'; export default function onTouchEnd(event) { const swiper = this; const data = swiper.touchEventsData; - const pointerIndex = data.evCache.findIndex((cachedEv) => cachedEv.pointerId === event.pointerId); - if (pointerIndex >= 0) { - data.evCache.splice(pointerIndex, 1); + + let e = event; + if (e.originalEvent) e = e.originalEvent; + let targetTouch; + const isTouchEvent = e.type === 'touchend' || e.type === 'touchcancel'; + if (!isTouchEvent) { + if (data.touchId !== null) return; // return from pointer if we use touch + if (e.pointerId !== data.pointerId) return; + targetTouch = e; + } else { + targetTouch = [...e.changedTouches].filter((t) => t.identifier === data.touchId)[0]; + if (!targetTouch || targetTouch.identifier !== data.touchId) return; } - if (['pointercancel', 'pointerout', 'pointerleave', 'contextmenu'].includes(event.type)) { + + data.pointerId = null; + data.touchId = null; + if (['pointercancel', 'pointerout', 'pointerleave', 'contextmenu'].includes(e.type)) { const proceed = - ['pointercancel', 'contextmenu'].includes(event.type) && + ['pointercancel', 'contextmenu'].includes(e.type) && (swiper.browser.isSafari || swiper.browser.isWebView); if (!proceed) { return; @@ -18,10 +30,8 @@ export default function onTouchEnd(event) { const { params, touches, rtlTranslate: rtl, slidesGrid, enabled } = swiper; if (!enabled) return; - if (!params.simulateTouch && event.pointerType === 'mouse') return; + if (!params.simulateTouch && e.pointerType === 'mouse') return; - let e = event; - if (e.originalEvent) e = e.originalEvent; if (data.allowTouchCallbacks) { swiper.emit('touchEnd', e); } diff --git a/src/core/events/onTouchMove.mjs b/src/core/events/onTouchMove.mjs index 0c0951a96..6c9ad99ce 100644 --- a/src/core/events/onTouchMove.mjs +++ b/src/core/events/onTouchMove.mjs @@ -11,6 +11,20 @@ export default function onTouchMove(event) { let e = event; if (e.originalEvent) e = e.originalEvent; + + if (e.type === 'pointermove') { + if (data.touchId !== null) return; // return from pointer if we use touch + const id = e.pointerId; + if (id !== data.pointerId) return; + } + let targetTouch; + if (e.type === 'touchmove') { + targetTouch = [...e.changedTouches].filter((t) => t.identifier === data.touchId)[0]; + if (!targetTouch || targetTouch.identifier !== data.touchId) return; + } else { + targetTouch = e; + } + if (!data.isTouched) { if (data.startMoving && data.isScrolling) { swiper.emit('touchMoveOpposite', e); @@ -18,9 +32,6 @@ export default function onTouchMove(event) { return; } - const pointerIndex = data.evCache.findIndex((cachedEv) => cachedEv.pointerId === e.pointerId); - if (pointerIndex >= 0) data.evCache[pointerIndex] = e; - const targetTouch = data.evCache.length > 1 ? data.evCache[0] : e; const pageX = targetTouch.pageX; const pageY = targetTouch.pageY; @@ -109,10 +120,7 @@ export default function onTouchMove(event) { data.startMoving = true; } } - if ( - data.isScrolling || - (swiper.zoom && swiper.params.zoom && swiper.params.zoom.enabled && data.evCache.length > 1) - ) { + if (data.isScrolling || (swiper.zoom && swiper.params.zoom && swiper.params.zoom.enabled)) { data.isTouched = false; return; } diff --git a/src/core/events/onTouchStart.mjs b/src/core/events/onTouchStart.mjs index c4a991b89..7fc6becd4 100644 --- a/src/core/events/onTouchStart.mjs +++ b/src/core/events/onTouchStart.mjs @@ -15,16 +15,47 @@ function closestElement(selector, base = this) { return __closestFrom(base); } +function preventEdgeSwipe(swiper, event, startX) { + const window = getWindow(); + const { params } = swiper; + const edgeSwipeDetection = params.edgeSwipeDetection; + const edgeSwipeThreshold = params.edgeSwipeThreshold; + if ( + edgeSwipeDetection && + (startX <= edgeSwipeThreshold || startX >= window.innerWidth - edgeSwipeThreshold) + ) { + if (edgeSwipeDetection === 'prevent') { + event.preventDefault(); + return true; + } + return false; + } + return true; +} + export default function onTouchStart(event) { const swiper = this; const document = getDocument(); - const window = getWindow(); - + let e = event; + if (e.originalEvent) e = e.originalEvent; const data = swiper.touchEventsData; - data.evCache.push(event); + if (e.type === 'pointerdown') { + if (data.pointerId !== null && data.pointerId !== e.pointerId) { + return; + } + data.pointerId = e.pointerId; + } else if (e.type === 'touchstart' && e.targetTouches.length === 1) { + data.touchId = e.targetTouches[0].identifier; + } + if (e.type === 'touchstart') { + // don't proceed touch event + preventEdgeSwipe(swiper, e, e.targetTouches[0].pageX); + return; + } + const { params, touches, enabled } = swiper; if (!enabled) return; - if (!params.simulateTouch && event.pointerType === 'mouse') return; + if (!params.simulateTouch && e.pointerType === 'mouse') return; if (swiper.animating && params.preventInteractionOnTransition) { return; @@ -32,8 +63,7 @@ export default function onTouchStart(event) { if (!swiper.animating && params.cssMode && params.loop) { swiper.loopFix(); } - let e = event; - if (e.originalEvent) e = e.originalEvent; + let targetEl = e.target; if (params.touchEventsTarget === 'wrapper') { @@ -46,7 +76,7 @@ export default function onTouchStart(event) { // change target el for shadow root component const swipingClassHasValue = !!params.noSwipingClass && params.noSwipingClass !== ''; // eslint-disable-next-line - const eventPath = event.composedPath ? event.composedPath() : event.path; + const eventPath = e.composedPath ? e.composedPath() : e.path; if (swipingClassHasValue && e.target && e.target.shadowRoot && eventPath) { targetEl = eventPath[0]; } @@ -78,17 +108,8 @@ export default function onTouchStart(event) { // Do NOT start if iOS edge swipe is detected. Otherwise iOS app cannot swipe-to-go-back anymore - const edgeSwipeDetection = params.edgeSwipeDetection || params.iOSEdgeSwipeDetection; - const edgeSwipeThreshold = params.edgeSwipeThreshold || params.iOSEdgeSwipeThreshold; - if ( - edgeSwipeDetection && - (startX <= edgeSwipeThreshold || startX >= window.innerWidth - edgeSwipeThreshold) - ) { - if (edgeSwipeDetection === 'prevent') { - event.preventDefault(); - } else { - return; - } + if (!preventEdgeSwipe(swiper, e, startX)) { + return; } Object.assign(data, {