From a6c3152e31f510547237c1913280abb07207b9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Unneb=C3=A4ck?= Date: Mon, 7 Nov 2022 22:34:00 +0100 Subject: [PATCH 1/6] Working touch concept --- index.js | 109 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 49 deletions(-) diff --git a/index.js b/index.js index c01dcec..026f1e3 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,3 @@ -const PanResponder = require('react-panresponder-web') const React = require('react') const { useSpring, animated } = require('@react-spring/web') @@ -149,55 +148,68 @@ const TinderCard = React.forwardRef( ) let swipeThresholdFulfilledDirection = 'none' - const panResponder = React.useMemo( - () => - PanResponder.create({ - // Ask to be the responder: - onStartShouldSetPanResponder: (evt, gestureState) => true, - onStartShouldSetPanResponderCapture: (evt, gestureState) => true, - onMoveShouldSetPanResponder: (evt, gestureState) => true, - onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, - - onPanResponderGrant: (evt, gestureState) => { - // The gesture has started. - // Probably wont need this anymore as postion i relative to swipe! - setSpringTarget.start({ xyrot: [gestureState.dx, gestureState.dy, 0], config: physics.touchResponsive }) - }, - onPanResponderMove: (evt, gestureState) => { - // Check fulfillment - if (onSwipeRequirementFulfilled || onSwipeRequirementUnfulfilled) { - const dir = getSwipeDirection({ - x: swipeRequirementType === 'velocity' ? gestureState.vx : gestureState.dx, - y: swipeRequirementType === 'velocity' ? gestureState.vy : gestureState.dy - }) - if (dir !== swipeThresholdFulfilledDirection) { - swipeThresholdFulfilledDirection = dir - if (swipeThresholdFulfilledDirection === 'none') { - if (onSwipeRequirementUnfulfilled) onSwipeRequirementUnfulfilled() - } else { - if (onSwipeRequirementFulfilled) onSwipeRequirementFulfilled(dir) - } - } - } - // use guestureState.vx / guestureState.vy for velocity calculations - // translate element - let rot = ((300 * gestureState.vx) / width) * 15// Magic number 300 different on different devices? Run on physical device! - rot = Math.max(Math.min(rot, settings.maxTilt), -settings.maxTilt) - setSpringTarget.start({ xyrot: [gestureState.dx, gestureState.dy, rot], config: physics.touchResponsive }) - }, - onPanResponderTerminationRequest: (evt, gestureState) => { - return true - }, - onPanResponderRelease: (evt, gestureState) => { - // The user has released all touches while this view is the - // responder. This typically means a gesture has succeeded - // enable - handleSwipeReleased(setSpringTarget, gestureState) + React.useLayoutEffect(() => { + let startPositon = { x: 0, y: 0 } + let lastPosition = { dx: 0, dy: 0, vx: 0, vy: 0, timeStamp: Date.now() } + + element.current.addEventListener(('touchstart'), (ev) => { + const dx = ev.touches[0].clientX - startPositon.x + const dy = ev.touches[0].clientY - startPositon.y + + const vx = -(dx - lastPosition.dx) / (lastPosition.timeStamp - Date.now()) + const vy = -(dy - lastPosition.dy) / (lastPosition.timeStamp - Date.now()) + + const gestureState = { dx, dy, vx, vy, timeStamp: Date.now() } + lastPosition = gestureState + + startPositon = { x: ev.touches[0].clientX, y: ev.touches[0].clientY } + }) + + element.current.addEventListener(('mousedown'), (ev) => { + // TODO + }) + + element.current.addEventListener(('touchmove'), (ev) => { + const dx = ev.touches[0].clientX - startPositon.x + const dy = ev.touches[0].clientY - startPositon.y + + const vx = -(dx - lastPosition.dx) / (lastPosition.timeStamp - Date.now()) + const vy = -(dy - lastPosition.dy) / (lastPosition.timeStamp - Date.now()) + + const gestureState = { dx, dy, vx, vy, timeStamp: Date.now() } + + lastPosition = gestureState + + // Check fulfillment + if (onSwipeRequirementFulfilled || onSwipeRequirementUnfulfilled) { + const dir = getSwipeDirection({ + x: swipeRequirementType === 'velocity' ? gestureState.vx : gestureState.dx, + y: swipeRequirementType === 'velocity' ? gestureState.vy : gestureState.dy + }) + if (dir !== swipeThresholdFulfilledDirection) { + swipeThresholdFulfilledDirection = dir + if (swipeThresholdFulfilledDirection === 'none') { + if (onSwipeRequirementUnfulfilled) onSwipeRequirementUnfulfilled() + } else { + if (onSwipeRequirementFulfilled) onSwipeRequirementFulfilled(dir) + } } - }), - [] - ) + } + + // use guestureState.vx / guestureState.vy for velocity calculations + // translate element + let rot = ((300 * gestureState.vx) / width) * 15// Magic number 300 different on different devices? Run on physical device! + rot = Math.max(Math.min(rot, settings.maxTilt), -settings.maxTilt) + setSpringTarget.start({ xyrot: [gestureState.dx, gestureState.dy, rot], config: physics.touchResponsive }) + }) + + element.current.addEventListener(('touchend'), (ev) => { + handleSwipeReleased(setSpringTarget, lastPosition) + startPositon = { x: 0, y: 0 } + lastPosition = { dx: 0, dy: 0, vx: 0, vy: 0, timeStamp: Date.now() } + }) + }) const element = React.useRef() @@ -213,7 +225,6 @@ const TinderCard = React.forwardRef( React.createElement(AnimatedDiv, { ref: element, className, - ...panResponder.panHandlers, style: { transform: xyrot.to((x, y, rot) => `translate3d(${x}px, ${y}px, ${0}px) rotate(${rot}deg)`) }, From 5e826109fac2e40760a75ee39eff1ca4897bb50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Unneb=C3=A4ck?= Date: Mon, 7 Nov 2022 22:45:57 +0100 Subject: [PATCH 2/6] add gestureStateFromWebTouchEvent --- index.js | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/index.js b/index.js index 026f1e3..db4f2c8 100644 --- a/index.js +++ b/index.js @@ -149,18 +149,27 @@ const TinderCard = React.forwardRef( let swipeThresholdFulfilledDirection = 'none' + const gestureStateFromWebTouchEvent = (ev, startPositon, lastPosition) => { + const dx = ev.touches[0].clientX - startPositon.x + const dy = ev.touches[0].clientY - startPositon.y + + const vx = -(dx - lastPosition.dx) / (lastPosition.timeStamp - Date.now()) + const vy = -(dy - lastPosition.dy) / (lastPosition.timeStamp - Date.now()) + + const gestureState = { dx, dy, vx, vy, timeStamp: Date.now() } + return gestureState + } + React.useLayoutEffect(() => { let startPositon = { x: 0, y: 0 } let lastPosition = { dx: 0, dy: 0, vx: 0, vy: 0, timeStamp: Date.now() } element.current.addEventListener(('touchstart'), (ev) => { - const dx = ev.touches[0].clientX - startPositon.x - const dy = ev.touches[0].clientY - startPositon.y - - const vx = -(dx - lastPosition.dx) / (lastPosition.timeStamp - Date.now()) - const vy = -(dy - lastPosition.dy) / (lastPosition.timeStamp - Date.now()) + if (!ev.srcElement.className.includes('pressable') && ev.cancelable) { + ev.preventDefault() + } - const gestureState = { dx, dy, vx, vy, timeStamp: Date.now() } + const gestureState = gestureStateFromWebTouchEvent(ev, startPositon, lastPosition) lastPosition = gestureState startPositon = { x: ev.touches[0].clientX, y: ev.touches[0].clientY } @@ -171,13 +180,7 @@ const TinderCard = React.forwardRef( }) element.current.addEventListener(('touchmove'), (ev) => { - const dx = ev.touches[0].clientX - startPositon.x - const dy = ev.touches[0].clientY - startPositon.y - - const vx = -(dx - lastPosition.dx) / (lastPosition.timeStamp - Date.now()) - const vy = -(dy - lastPosition.dy) / (lastPosition.timeStamp - Date.now()) - - const gestureState = { dx, dy, vx, vy, timeStamp: Date.now() } + const gestureState = gestureStateFromWebTouchEvent(ev, startPositon, lastPosition) lastPosition = gestureState @@ -213,14 +216,6 @@ const TinderCard = React.forwardRef( const element = React.useRef() - React.useLayoutEffect(() => { - element.current.addEventListener(('touchstart'), (ev) => { - if (!ev.srcElement.className.includes('pressable') && ev.cancelable) { - ev.preventDefault() - } - }) - }) - return ( React.createElement(AnimatedDiv, { ref: element, From 4ec55eec56c03ebdc82db76357725ecadeb7545a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Unneb=C3=A4ck?= Date: Mon, 7 Nov 2022 23:07:57 +0100 Subject: [PATCH 3/6] Mouse support --- index.js | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index db4f2c8..1319637 100644 --- a/index.js +++ b/index.js @@ -149,9 +149,9 @@ const TinderCard = React.forwardRef( let swipeThresholdFulfilledDirection = 'none' - const gestureStateFromWebTouchEvent = (ev, startPositon, lastPosition) => { - const dx = ev.touches[0].clientX - startPositon.x - const dy = ev.touches[0].clientY - startPositon.y + const gestureStateFromWebEvent = (ev, startPositon, lastPosition, isTouch) => { + const dx = isTouch ? ev.touches[0].clientX - startPositon.x : ev.clientX - startPositon.x + const dy = isTouch ? ev.touches[0].clientY - startPositon.y : ev.clientY - startPositon.y const vx = -(dx - lastPosition.dx) / (lastPosition.timeStamp - Date.now()) const vy = -(dy - lastPosition.dy) / (lastPosition.timeStamp - Date.now()) @@ -163,27 +163,26 @@ const TinderCard = React.forwardRef( React.useLayoutEffect(() => { let startPositon = { x: 0, y: 0 } let lastPosition = { dx: 0, dy: 0, vx: 0, vy: 0, timeStamp: Date.now() } + let isClicking = false element.current.addEventListener(('touchstart'), (ev) => { if (!ev.srcElement.className.includes('pressable') && ev.cancelable) { ev.preventDefault() } - const gestureState = gestureStateFromWebTouchEvent(ev, startPositon, lastPosition) + const gestureState = gestureStateFromWebEvent(ev, startPositon, lastPosition, true) lastPosition = gestureState - startPositon = { x: ev.touches[0].clientX, y: ev.touches[0].clientY } }) element.current.addEventListener(('mousedown'), (ev) => { - // TODO - }) - - element.current.addEventListener(('touchmove'), (ev) => { - const gestureState = gestureStateFromWebTouchEvent(ev, startPositon, lastPosition) - + isClicking = true + const gestureState = gestureStateFromWebEvent(ev, startPositon, lastPosition, false) lastPosition = gestureState + startPositon = { x: ev.clientX, y: ev.clientY } + }) + const handleMove = (gestureState) => { // Check fulfillment if (onSwipeRequirementFulfilled || onSwipeRequirementUnfulfilled) { const dir = getSwipeDirection({ @@ -202,9 +201,29 @@ const TinderCard = React.forwardRef( // use guestureState.vx / guestureState.vy for velocity calculations // translate element - let rot = ((300 * gestureState.vx) / width) * 15// Magic number 300 different on different devices? Run on physical device! + let rot = gestureState.vx * 15 // Magic number 15 looks about right rot = Math.max(Math.min(rot, settings.maxTilt), -settings.maxTilt) setSpringTarget.start({ xyrot: [gestureState.dx, gestureState.dy, rot], config: physics.touchResponsive }) + } + + window.addEventListener(('mousemove'), (ev) => { + if (!isClicking) return + const gestureState = gestureStateFromWebEvent(ev, startPositon, lastPosition, false) + lastPosition = gestureState + handleMove(gestureState) + }) + + window.addEventListener(('mouseup'), (ev) => { + isClicking = false + handleSwipeReleased(setSpringTarget, lastPosition) + startPositon = { x: 0, y: 0 } + lastPosition = { dx: 0, dy: 0, vx: 0, vy: 0, timeStamp: Date.now() } + }) + + element.current.addEventListener(('touchmove'), (ev) => { + const gestureState = gestureStateFromWebEvent(ev, startPositon, lastPosition, true) + lastPosition = gestureState + handleMove(gestureState) }) element.current.addEventListener(('touchend'), (ev) => { From 9bc595485a60dfb0c4861d140c272cef13068d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Unneb=C3=A4ck?= Date: Mon, 7 Nov 2022 23:10:40 +0100 Subject: [PATCH 4/6] Fix jittering first touch --- index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 1319637..cdf6f6f 100644 --- a/index.js +++ b/index.js @@ -150,8 +150,14 @@ const TinderCard = React.forwardRef( let swipeThresholdFulfilledDirection = 'none' const gestureStateFromWebEvent = (ev, startPositon, lastPosition, isTouch) => { - const dx = isTouch ? ev.touches[0].clientX - startPositon.x : ev.clientX - startPositon.x - const dy = isTouch ? ev.touches[0].clientY - startPositon.y : ev.clientY - startPositon.y + let dx = isTouch ? ev.touches[0].clientX - startPositon.x : ev.clientX - startPositon.x + let dy = isTouch ? ev.touches[0].clientY - startPositon.y : ev.clientY - startPositon.y + + // We cant calculate velocity from the first event + if (startPositon.x === 0 && startPositon.y === 0) { + dx = 0 + dy = 0 + } const vx = -(dx - lastPosition.dx) / (lastPosition.timeStamp - Date.now()) const vy = -(dy - lastPosition.dy) / (lastPosition.timeStamp - Date.now()) From 9890e78ffc0c7fe609cb6704ccd89e36c2028879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Unneb=C3=A4ck?= Date: Mon, 7 Nov 2022 23:14:22 +0100 Subject: [PATCH 5/6] Fix accidental trigger swiped card --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index cdf6f6f..7746348 100644 --- a/index.js +++ b/index.js @@ -220,6 +220,7 @@ const TinderCard = React.forwardRef( }) window.addEventListener(('mouseup'), (ev) => { + if (!isClicking) return isClicking = false handleSwipeReleased(setSpringTarget, lastPosition) startPositon = { x: 0, y: 0 } From 723b3cb58e4ee578dc5d131bd739b0e099808369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Unneb=C3=A4ck?= Date: Mon, 7 Nov 2022 23:15:53 +0100 Subject: [PATCH 6/6] Remove react-panresponder-web --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index ac3b7ec..8a5cf55 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,7 @@ "test": "standard && ts-readme-generator --check" }, "dependencies": { - "p-sleep": "^1.1.0", - "react-panresponder-web": "^1.0.2" + "p-sleep": "^1.1.0" }, "devDependencies": { "@types/react": "^16.9.51",