How do I recognize swipe events in React?
Asked Answered
S

7

20

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?

Suffruticose answered 6/1, 2022 at 19:41 Comment(0)
S
49

Horiziontal swipes (left, right)

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}/>

Vertical swipes (up, down)

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.

Suffruticose answered 6/1, 2022 at 19:41 Comment(1)
Great solution! For better performances you can use refs instead of state tho. Using refs avoids unnecessary re-renders of your component is this case.Suspiration
T
13

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

  1. import useSwipe from "whatever-path/useSwipe";
  2. const swipeHandlers = useSwipe({ onSwipedLeft: () => console.log('left'), onSwipedRight: () => console.log('right') });
  3. <div {...swipeHandlers}>some swipeable div (or whatever html tag)</div>
Tindal answered 9/1, 2023 at 16:17 Comment(1)
Initially your solution didn't work for me. But then I tried dropping useState. I simply declared touchStart and touchEnd as mere variables that get initiliazed and later re-assigned with values, instead of using useState, setTouchStart and setTouchEnd. Worked like a charm! Thanks.Gerdy
U
6

@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
}
Unaccountable answered 13/12, 2022 at 15:35 Comment(0)
B
2

@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
  };
Balthasar answered 17/10, 2023 at 13:26 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Fransiscafransisco
C
1

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>
Cub answered 3/3, 2023 at 21:44 Comment(0)
I
0

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
Inadvertency answered 7/5, 2023 at 17:33 Comment(0)
D
0

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>
Dacca answered 1/1 at 4:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.