I'm currently developing a React app and I want to detect swipe events (left, right) on a div
element (on mobile devices).
How do I achieve this without any additional libraries?
I'm currently developing a React app and I want to detect swipe events (left, right) on a div
element (on mobile devices).
How do I achieve this without any additional libraries?
This code detects left and right swipe events, without having any impact on usual touch events.
const [touchStart, setTouchStart] = useState(null)
const [touchEnd, setTouchEnd] = useState(null)
// the required distance between touchStart and touchEnd to be detected as a swipe
const minSwipeDistance = 50
const onTouchStart = (e) => {
setTouchEnd(null) // otherwise the swipe is fired even with usual touch events
setTouchStart(e.targetTouches[0].clientX)
}
const onTouchMove = (e) => setTouchEnd(e.targetTouches[0].clientX)
const onTouchEnd = () => {
if (!touchStart || !touchEnd) return
const distance = touchStart - touchEnd
const isLeftSwipe = distance > minSwipeDistance
const isRightSwipe = distance < -minSwipeDistance
if (isLeftSwipe || isRightSwipe) console.log('swipe', isLeftSwipe ? 'left' : 'right')
// add your conditional logic here
}
<div onTouchStart={onTouchStart} onTouchMove={onTouchMove} onTouchEnd={onTouchEnd}/>
If you need to detect vertical swipes as well (up and down), you can use e.targetTouches[0].clientY
(see docs) in a similar manner.
Excellent solution provided by @gru. I just encapsulated it into a custom hook to be simpler to integrate between different components.
useSwipe.tsx
import {TouchEvent, useState} from "react";
interface SwipeInput {
onSwipedLeft: () => void
onSwipedRight: () => void
}
interface SwipeOutput {
onTouchStart: (e: TouchEvent) => void
onTouchMove: (e: TouchEvent) => void
onTouchEnd: () => void
}
export default (input: SwipeInput): SwipeOutput => {
const [touchStart, setTouchStart] = useState(0);
const [touchEnd, setTouchEnd] = useState(0);
const minSwipeDistance = 50;
const onTouchStart = (e: TouchEvent) => {
setTouchEnd(0); // otherwise the swipe is fired even with usual touch events
setTouchStart(e.targetTouches[0].clientX);
}
const onTouchMove = (e: TouchEvent) => setTouchEnd(e.targetTouches[0].clientX);
const onTouchEnd = () => {
if (!touchStart || !touchEnd) return;
const distance = touchStart - touchEnd;
const isLeftSwipe = distance > minSwipeDistance;
const isRightSwipe = distance < -minSwipeDistance;
if (isLeftSwipe) {
input.onSwipedLeft();
}
if (isRightSwipe) {
input.onSwipedRight();
}
}
return {
onTouchStart,
onTouchMove,
onTouchEnd
}
}
can be integrated in different components like this
import useSwipe from "whatever-path/useSwipe";
const swipeHandlers = useSwipe({ onSwipedLeft: () => console.log('left'), onSwipedRight: () => console.log('right') });
<div {...swipeHandlers}>some swipeable div (or whatever html tag)</div>
@gru's code works great, the only downside is that it also detects a swipe when the user tries to scroll down and moves slightly to the side as well.
My changes makes sure the swipe is only detected when the horizontal movement is greater than the vertical movement:
const distanceX = touchStartX - touchEndX
const distanceY = touchStartY - touchEndY
const isLeftSwipe = distanceX > minSwipeDistance
const isRightSwipe = distanceX < -minSwipeDistance
if (isRightSwipe && Math.abs(distanceX) > distanceY) {
// add your conditional logic here
}
if (isLeftSwipe && distanceX > distanceY) {
// add your conditional logic here
}
@gru Here is an updated version with ref for better performances.
const touchStart = useRef<number | null>(null);
const touchEnd = useRef<number | null>(null);
// the required distance between touchStart and touchEnd to be detected as a swipe
const minSwipeDistance = 50;
const onTouchStart = (e: React.TouchEvent) => {
touchEnd.current = null;
touchStart.current = e.targetTouches[0].clientX;
};
const onTouchMove = (e: React.TouchEvent) => {
touchEnd.current = e.targetTouches[0].clientX;
};
const onTouchEnd = () => {
if (!touchStart.current || !touchEnd.current) return;
const distance = touchStart.current - touchEnd.current;
const isLeftSwipe = distance > minSwipeDistance;
const isRightSwipe = distance < -minSwipeDistance;
if (isLeftSwipe || isRightSwipe)
console.log('swipe', isLeftSwipe ? 'left' : 'right');
// add your conditional logic here
};
Inspired by the answer of dear gru, I adapted it to React Native. The code is as below. (It finds vertical distance, changing pageY to pageX will find the horizontal distance as well)
const [touchStart, setTouchStart] = useState(null);
const [touchEnd, setTouchEnd] = useState(null);
const minSwipeDistance = 50;
const onTouchStart = (e) => {
setTouchEnd(null);
setTouchStart(e.nativeEvent.touches[0].pageY);
}
const onTouchMove = (e) => {
setTouchEnd(e.nativeEvent.touches[0].pageY);
}
const onTouchEnd = () => {
if (!touchStart || !touchEnd) return;
if (touchStart - touchEnd > minSwipeDistance) {
// Do something
}
}
<View onTouchStart={onTouchStart} onTouchMove={onTouchMove} onTouchEnd={onTouchEnd} >
...
</View>
I Got the above example made for @E L and implemented swiperUp/swiperDown. ;)
import { TouchEvent, useState } from 'react'
interface SwipeInput {
onSwipedLeft: () => void
onSwipedRight: () => void
onSwipedUp: () => void
onSwipedDown: () => void
}
interface SwipeOutput {
onTouchStart: (e: TouchEvent) => void
onTouchMove: (e: TouchEvent) => void
onTouchEnd: () => void
}
const MySwipe = (input: SwipeInput): SwipeOutput => {
const minSwipeDistance = 50
const [touchStart, setTouchStart] = useState<number>(0)
const [touchEnd, setTouchEnd] = useState<number>(0)
const [touchVertStart, setTouchVertStart] = useState<number>(0)
const [touchVertEnd, setTouchVertEnd] = useState<number>(0)
const onTouchStart = (e: TouchEvent) => {
setTouchEnd(0)
setTouchStart(e.targetTouches[0].clientX)
setTouchVertEnd(0)
setTouchVertStart(e.targetTouches[0].clientY)
}
const onTouchMove = (e: TouchEvent) => {
setTouchEnd(e.targetTouches[0].clientX)
setTouchVertEnd(e.targetTouches[0].clientY)
}
const onTouchEnd = () => {
if (!touchStart || !touchEnd) return
if (!touchVertStart || !touchVertEnd) return
const distance = touchStart - touchEnd
const isLeftSwipe = distance > minSwipeDistance
const distanceVert = touchVertStart - touchVertEnd
const isUpSwipe = distanceVert > minSwipeDistance
if (Math.abs(touchStart - touchEnd) > Math.abs(touchVertStart - touchVertEnd)) {
if (isLeftSwipe) {
input.onSwipedLeft()
} else {
input.onSwipedRight()
}
} else {
if (isUpSwipe) {
input.onSwipedUp()
} else {
input.onSwipedDown()
}
}
}
return {
onTouchStart,
onTouchMove,
onTouchEnd
}
}
export default MySwipe
Since I stole all the code from this post, here's a full React/TypeScript component that implements this code:
'use client'
import React, {useState, ReactNode} from "react";
// https://mcmap.net/q/609781/-how-do-i-recognize-swipe-events-in-react
export default function Swipeable(props:SwipeableProps) {
const [touchStartX, setTouchStartX] = useState(null)
const [touchEndX, setTouchEndX] = useState(null)
const [touchStartY, setTouchStartY] = useState(null)
const [touchEndY, setTouchEndY] = useState(null)
const minSwipeDistance = 50
function onTouchStart(e) {
setTouchEndX(null)
setTouchStartX(e.targetTouches[0].clientX)
setTouchEndY(null)
setTouchStartY(e.targetTouches[0].clientY)
}
function onTouchMove(e) {
setTouchEndX(e.targetTouches[0].clientX)
setTouchEndY(e.targetTouches[0].clientY)
}
function onTouchEnd() {
if (touchStartX && touchEndX) swipeHorizontal()
if (touchStartY && touchEndY) swipeVertical()
}
function swipeHorizontal() {
const xDistance = touchStartX - touchEndX
const yDistance = touchStartY - touchEndY
if (Math.abs(yDistance) >= Math.abs(xDistance)) {
return;
}
const isLeftSwipe = xDistance > minSwipeDistance
const isRightSwipe = xDistance < -minSwipeDistance
if (isLeftSwipe && props.onSwipeLeft) {
props.onSwipeLeft();
}
if (isRightSwipe && props.onSwipeRight) {
props.onSwipeRight();
}
}
function swipeVertical() {
const xDistance = touchStartX - touchEndX
const yDistance = touchStartY - touchEndY
if (Math.abs(xDistance) >= Math.abs(yDistance)) {
return;
}
const isUpSwipe = yDistance > minSwipeDistance
const isDownSipe = yDistance < -minSwipeDistance
if (isDownSipe && props.onSwipeDown) {
props.onSwipeDown();
}
if (isUpSwipe && props.onSwipeUp) {
props.onSwipeUp();
}
}
return (
<div onTouchStart={onTouchStart} onTouchMove={onTouchMove} onTouchEnd={onTouchEnd}>
{props.children}
</div>
)
}
export interface SwipeableProps {
children:ReactNode,
onSwipeLeft?: () => void,
onSwipeRight?: () => void,
onSwipeUp?: () => void,
onSwipeDown?: () => void
}
Usage
<Swipeable onSwipeLeft={left} onSwipeRight={right} onSwipeUp={up} onSwipeDown={down}>
Children Go Here
</Swipeable>
© 2022 - 2024 — McMap. All rights reserved.