ChatGPT解决这个技术问题 Extra ChatGPT

Detect a finger swipe through JavaScript on the iPhone and Android

How can you detect that a user swiped his finger in some direction over a web page with JavaScript?

I was wondering if there was one solution that would work for websites on both the iPhone and an Android phone.

For swipe recognition, I would recommend Hammer.js. It's quite small, and it supports many gestures: - Swipe - Rotate - Pinch - Press (long hold) - Tap - Pan
There is an event: "touchmove"
@Clay that one still does not work in Safari so no iPhone.
@JohnDoherty I agree it is great!

N
Niket Pathak

Simple vanilla JS code sample:

document.addEventListener('touchstart', handleTouchStart, false);        
document.addEventListener('touchmove', handleTouchMove, false);

var xDown = null;                                                        
var yDown = null;

function getTouches(evt) {
  return evt.touches ||             // browser API
         evt.originalEvent.touches; // jQuery
}                                                     
                                                                         
function handleTouchStart(evt) {
    const firstTouch = getTouches(evt)[0];                                      
    xDown = firstTouch.clientX;                                      
    yDown = firstTouch.clientY;                                      
};                                                
                                                                         
function handleTouchMove(evt) {
    if ( ! xDown || ! yDown ) {
        return;
    }

    var xUp = evt.touches[0].clientX;                                    
    var yUp = evt.touches[0].clientY;

    var xDiff = xDown - xUp;
    var yDiff = yDown - yUp;
                                                                         
    if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
        if ( xDiff > 0 ) {
            /* right swipe */ 
        } else {
            /* left swipe */
        }                       
    } else {
        if ( yDiff > 0 ) {
            /* down swipe */ 
        } else { 
            /* up swipe */
        }                                                                 
    }
    /* reset values */
    xDown = null;
    yDown = null;                                             
};

Tested in Android.


Looks cool and simple, any idea what is the support for this events touchstart, touchmove ?
It works pretty well but has a problem detecting straight movements. I will post another answer at this topic that fixed this as JQuery (desktop) solution. It also adds the mouse version of these swipe events and add a sensitivity option.
Damn. Topic is closed so cannot add my answer!
This works great, but left/right and up/down are backwards.
originalEvent is a JQuery property. It should be left out if you run pure javascript without JQuery. The current code raises an exception if run without JQuery.
D
Damian Pavlica

Simple vanilla JS example for horizontal swipe:

let touchstartX = 0
let touchendX = 0
    
function checkDirection() {
  if (touchendX < touchstartX) alert('swiped left!')
  if (touchendX > touchstartX) alert('swiped right!')
}

document.addEventListener('touchstart', e => {
  touchstartX = e.changedTouches[0].screenX
})

document.addEventListener('touchend', e => {
  touchendX = e.changedTouches[0].screenX
  checkDirection()
})

You can use pretty same logic for vertical swipe.


Lol this is so simple and even allows to specify a "travel distance".
Best answer by far.. it's a shame it doesn't have more upvotes..
@MattiaRasulo maybe need to add up and down swipe
this is some real nice code, if you want to do up and down all you need to do is change where it states X and replace with Y. Easyyyy
Great answer because of its simplicity! It may be worth noting that also vertical swipes are detected as horizontal swipes with this code if touch points have a minimal x distance.
J
John Doherty

I merged a few of the answers here into a script that uses CustomEvent to fire swiped events in the DOM. Add the 0.7k swiped-events.min.js script to your page and listen for swiped events:

swiped

document.addEventListener('swiped', function(e) {
    console.log(e.target); // the element that was swiped
    console.log(e.detail.dir); // swiped direction
});

swiped-left

document.addEventListener('swiped-left', function(e) {
    console.log(e.target); // the element that was swiped
});

swiped-right

document.addEventListener('swiped-right', function(e) {
    console.log(e.target); // the element that was swiped
});

swiped-up

document.addEventListener('swiped-up', function(e) {
    console.log(e.target); // the element that was swiped
});

swiped-down

document.addEventListener('swiped-down', function(e) {
    console.log(e.target); // the element that was swiped
});

You can also attach directly to an element:

document.getElementById('myBox').addEventListener('swiped-down', function(e) {
    console.log(e.target); // the element that was swiped
});

Optional config

You can specify the following attributes to tweak how swipe interaction functions in your page (these are optional).

<div data-swipe-threshold="10"
     data-swipe-timeout="1000"
     data-swipe-ignore="false">
      Swiper, get swiping!
</div>

To set defaults application wide, set config attributes on topmost element:

<body data-swipe-threshold="100" data-swipe-timeout="250">
    <div>Swipe me</div>
    <div>or me</div>
</body>

Source code is available on Github


I came here because pure-swipe was not working for me on MOBILE
@StefanBob if you raise a tick on the github repo with enough information to allow me to reproduce the issue, I will look into it
Thanks, it works perfectly! I replaced Hammer.js with your library, because the former doesn't work with browser zoom and that is a serious usability issue. With this library the zoom works properly (tested on Android)
Hammer.js doesn't seem to be maintained anymore
@JohnDoherty would you extend it so it also works on desktop(non-touch) devices.
M
Marwelln

Based on @givanse's answer, this is how you could do it with classes:

class Swipe {
    constructor(element) {
        this.xDown = null;
        this.yDown = null;
        this.element = typeof(element) === 'string' ? document.querySelector(element) : element;

        this.element.addEventListener('touchstart', function(evt) {
            this.xDown = evt.touches[0].clientX;
            this.yDown = evt.touches[0].clientY;
        }.bind(this), false);

    }

    onLeft(callback) {
        this.onLeft = callback;

        return this;
    }

    onRight(callback) {
        this.onRight = callback;

        return this;
    }

    onUp(callback) {
        this.onUp = callback;

        return this;
    }

    onDown(callback) {
        this.onDown = callback;

        return this;
    }

    handleTouchMove(evt) {
        if ( ! this.xDown || ! this.yDown ) {
            return;
        }

        var xUp = evt.touches[0].clientX;
        var yUp = evt.touches[0].clientY;

        this.xDiff = this.xDown - xUp;
        this.yDiff = this.yDown - yUp;

        if ( Math.abs( this.xDiff ) > Math.abs( this.yDiff ) ) { // Most significant.
            if ( this.xDiff > 0 ) {
                this.onLeft();
            } else {
                this.onRight();
            }
        } else {
            if ( this.yDiff > 0 ) {
                this.onUp();
            } else {
                this.onDown();
            }
        }

        // Reset values.
        this.xDown = null;
        this.yDown = null;
    }

    run() {
        this.element.addEventListener('touchmove', function(evt) {
            this.handleTouchMove(evt).bind(this);
        }.bind(this), false);
    }
}

You can than use it like this:

// Use class to get element by string.
var swiper = new Swipe('#my-element');
swiper.onLeft(function() { alert('You swiped left.') });
swiper.run();

// Get the element yourself.
var swiper = new Swipe(document.getElementById('#my-element'));
swiper.onLeft(function() { alert('You swiped left.') });
swiper.run();

// One-liner.
(new Swipe('#my-element')).onLeft(function() { alert('You swiped left.') }).run();

this code probably wont work because you'll get an exception while trying to call .bind of undefined because your handleTouchMove actually didn't return anything. also it's useless to call bind when calling function with this. because it's already bound to current context
I just removed .bind(this); and it worked gracefully. thank you @nicholas_r
Part get the element yourself I just remove '#' in document.getElementById('my-element') and it worked good. Thank you @Marwelln :)
If you want to wait until the swipe ENDS (meaning after they lift their finger or onmouseup), change touches[0] to changedTouches[0] and the event handler type handleTouchMove to handleTouchEnd
call run() twice and you get a nasty memory leak
n
nashcheez

I have found @givanse brilliant answer to be the most reliable and compatible across multiple mobile browsers for registering swipe actions.

However, there's a change in his code required to make it work in modern day mobile browsers that are using jQuery.

event.toucheswon't exist if jQuery is used and results in undefined and should be replaced by event.originalEvent.touches. Without jQuery, event.touches should work fine.

So the solution becomes,

document.addEventListener('touchstart', handleTouchStart, false);        
document.addEventListener('touchmove', handleTouchMove, false);

var xDown = null;                                                        
var yDown = null;                                                        

function handleTouchStart(evt) {                                         
    xDown = evt.originalEvent.touches[0].clientX;                                      
    yDown = evt.originalEvent.touches[0].clientY;                                      
};                                                

function handleTouchMove(evt) {
    if ( ! xDown || ! yDown ) {
        return;
    }

    var xUp = evt.originalEvent.touches[0].clientX;                                    
    var yUp = evt.originalEvent.touches[0].clientY;

    var xDiff = xDown - xUp;
    var yDiff = yDown - yUp;

    if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
        if ( xDiff > 0 ) {
            /* left swipe */ 
        } else {
            /* right swipe */
        }                       
    } else {
        if ( yDiff > 0 ) {
            /* up swipe */ 
        } else { 
            /* down swipe */
        }                                                                 
    }
    /* reset values */
    xDown = null;
    yDown = null;                                             
};

Tested on:

Android: Chrome, UC Browser

iOS: Safari, Chrome, UC Browser


originalEvent is a JQuery property. It does not even exist in pure Javascript.
As per this SO answer, a touch event if supported by the browser will be exposed through event.originalEvent. The thing is event.touches has ceased to exist now and results in undefined.
event.touches only ceased to exist when using JQuery. Try your code without JQuery and you will get an error that evt.originalEvent is undefined. JQuery totally replaces event with its own and puts the native browser event in originalevent. Short version: Your code only works with JQuery. It works without JQuery if you remove originalevent.
Yeah I researched a bit and realized you were right about the availability of jquery enabling event.originalEvent. I will update my answer. Thanks! :)
Changed xDown = evt.originalEvent.touches[0].clientX; yDown = evt.originalEvent.touches[0].clientY; to xDown = evt.offsetX; yDown = evt.offsetY; and now it works like charm on normal JS. I like this solution.
a
alex

what i've used before is you have to detect the mousedown event, record its x,y location (whichever is relevant) then detect the mouseup event, and subtract the two values.


I believe it's touchstart, touchmove, touchcancel, and touchend that one would work with, not mousedown or mouseup.
N
Niklas Ekman

jQuery Mobile also includes swipe support: http://api.jquerymobile.com/swipe/

Example

$("#divId").on("swipe", function(event) {
    alert("It's a swipe!");
});

If one does not want jQuery mobile to manipulate the UI, see: stackoverflow.com/questions/8648596/…
r
rmnsh

Some mod of uppest answer(can't comment...) to deal with to short swipes

document.addEventListener('touchstart', handleTouchStart, false);        
document.addEventListener('touchmove', handleTouchMove, false);
var xDown = null;                                                        
var yDown = null;                                                        
function handleTouchStart(evt) {                                         
    xDown = evt.touches[0].clientX;                                      
    yDown = evt.touches[0].clientY;                                      
};                                                
function handleTouchMove(evt) {
    if ( ! xDown || ! yDown ) {
        return;
    }

    var xUp = evt.touches[0].clientX;                                    
    var yUp = evt.touches[0].clientY;

    var xDiff = xDown - xUp;
    var yDiff = yDown - yUp;
    if(Math.abs( xDiff )+Math.abs( yDiff )>150){ //to deal with to short swipes

    if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
        if ( xDiff > 0 ) {/* left swipe */ 
            alert('left!');
        } else {/* right swipe */
            alert('right!');
        }                       
    } else {
        if ( yDiff > 0 ) {/* up swipe */
            alert('Up!'); 
        } else { /* down swipe */
            alert('Down!');
        }                                                                 
    }
    /* reset values */
    xDown = null;
    yDown = null;
    }
};

M
Marc-André Lafortune

I've repackaged TouchWipe as a short jquery plugin: detectSwipe


K
K-Gun

threshold, timeout swipe, swipeBlockElems add.

document.addEventListener('touchstart', handleTouchStart, false);
document.addEventListener('touchmove', handleTouchMove, false);
document.addEventListener('touchend', handleTouchEnd, false);     

const SWIPE_BLOCK_ELEMS = [
  'swipBlock',
  'handle',
  'drag-ruble'
]

let xDown = null;
let yDown = null; 
let xDiff = null;
let yDiff = null;
let timeDown = null;
const  TIME_THRESHOLD = 200;
const  DIFF_THRESHOLD = 130;

function handleTouchEnd() {

let timeDiff = Date.now() - timeDown; 
if (Math.abs(xDiff) > Math.abs(yDiff)) { /*most significant*/
  if (Math.abs(xDiff) > DIFF_THRESHOLD && timeDiff < TIME_THRESHOLD) {
    if (xDiff > 0) {
      // console.log(xDiff, TIME_THRESHOLD, DIFF_THRESHOLD)
      SWIPE_LEFT(LEFT) /* left swipe */
    } else {
      // console.log(xDiff)
      SWIPE_RIGHT(RIGHT) /* right swipe */
    }
  } else {
    console.log('swipeX trashhold')
  }
} else {
  if (Math.abs(yDiff) > DIFF_THRESHOLD && timeDiff < TIME_THRESHOLD) {
    if (yDiff > 0) {
      /* up swipe */
    } else {
      /* down swipe */
    }
  } else {
    console.log('swipeY trashhold')
  }
 }
 /* reset values */
 xDown = null;
 yDown = null;
 timeDown = null; 
}
function containsClassName (evntarget , classArr) {
 for (var i = classArr.length - 1; i >= 0; i--) {
   if( evntarget.classList.contains(classArr[i]) ) {
      return true;
    }
  }
}
function handleTouchStart(evt) {
  let touchStartTarget = evt.target;
  if( containsClassName(touchStartTarget, SWIPE_BLOCK_ELEMS) ) {
    return;
  }
  timeDown = Date.now()
  xDown = evt.touches[0].clientX;
  yDown = evt.touches[0].clientY;
  xDiff = 0;
  yDiff = 0;

}

function handleTouchMove(evt) {
  if (!xDown || !yDown) {
    return;
  }

  var xUp = evt.touches[0].clientX;
  var yUp = evt.touches[0].clientY;


  xDiff = xDown - xUp;
  yDiff = yDown - yUp;
}

B
Basj

I wanted to detect left and right swipe only, but trigger the action only when the touch event ends, so I slightly modified the @givanse's great answer to do that.

Why to do that? If for example, while swiping, the user notices he finally doesn't want to swipe, he can move his finger at the original position (a very popular "dating" phone application does this ;)), and then the "swipe right" event is cancelled.

So in order to avoid a "swipe right" event just because there is a 3px difference horizontally, I added a threshold under which an event is discarded: in order to have a "swipe right" event, the user has to swipe of at least 1/3 of the browser width (of course you can modify this).

All these small details enhance the user experience.

Note that currently, a "touch pinch zoom" might be detected as a swipe if one of the two fingers does a big horizontal move during the pinch zoom.

Here is the (Vanilla JS) code:

var xDown = null, yDown = null, xUp = null, yUp = null;
document.addEventListener('touchstart', touchstart, false);        
document.addEventListener('touchmove', touchmove, false);
document.addEventListener('touchend', touchend, false);
function touchstart(evt) { const firstTouch = (evt.touches || evt.originalEvent.touches)[0]; xDown = firstTouch.clientX; yDown = firstTouch.clientY; }
function touchmove(evt) { if (!xDown || !yDown ) return; xUp = evt.touches[0].clientX; yUp = evt.touches[0].clientY; }
function touchend(evt) { 
    var xDiff = xUp - xDown, yDiff = yUp - yDown;
    if ((Math.abs(xDiff) > Math.abs(yDiff)) && (Math.abs(xDiff) > 0.33 * document.body.clientWidth)) { 
        if (xDiff < 0) 
            document.getElementById('leftnav').click();
        else
            document.getElementById('rightnav').click();
    } 
    xDown = null, yDown = null;
}

R
Rayjax

If anyone is trying to use jQuery Mobile on Android and has problems with JQM swipe detection

(I had some on Xperia Z1, Galaxy S3, Nexus 4 and some Wiko phones too) this can be useful :

 //Fix swipe gesture on android
    if(android){ //Your own device detection here
        $.event.special.swipe.verticalDistanceThreshold = 500
        $.event.special.swipe.horizontalDistanceThreshold = 10
    }

Swipe on android wasn't detected unless it was a very long, precise and fast swipe.

With these two lines it works correctly


I also needed to add: $.event.special.swipe.scrollSupressionThreshold = 8; but you put me in the right direction! Thank you!
T
Trendal Toews

I had trouble with touchend handler firing continuously while the user was dragging a finger around. I don't know if that's due to something I'm doing wrong or not but I rewired this to accumulate moves with touchmove and touchend actually fires the callback.

I also needed to have a large number of these instances and so I added enable/disable methods.

And a threshold where a short swipe doesn't fire. Touchstart zero's the counters each time.

You can change the target_node on the fly. Enable on creation is optional.

/** Usage: */
touchevent = new Modules.TouchEventClass(callback, target_node);
touchevent.enable();
touchevent.disable();

/** 
*
*   Touch event module
*
*   @param method   set_target_mode
*   @param method   __touchstart
*   @param method   __touchmove
*   @param method   __touchend
*   @param method   enable
*   @param method   disable
*   @param function callback
*   @param node     target_node
*/
Modules.TouchEventClass = class {

    constructor(callback, target_node, enable=false) {

        /** callback function */
        this.callback = callback;

        this.xdown = null;
        this.ydown = null;
        this.enabled = false;
        this.target_node = null;

        /** move point counts [left, right, up, down] */
        this.counts = [];

        this.set_target_node(target_node);

        /** Enable on creation */
        if (enable === true) {
            this.enable();
        }

    }

    /** 
    *   Set or reset target node
    *
    *   @param string/node target_node
    *   @param string      enable (optional)
    */
    set_target_node(target_node, enable=false) {

        /** check if we're resetting target_node */
        if (this.target_node !== null) {

            /** remove old listener */
           this.disable();
        }

        /** Support string id of node */
        if (target_node.nodeName === undefined) {
            target_node = document.getElementById(target_node);
        }

        this.target_node = target_node;

        if (enable === true) {
            this.enable();
        }
    }

    /** enable listener */
    enable() {
        this.enabled = true;
        this.target_node.addEventListener("touchstart", this.__touchstart.bind(this));
        this.target_node.addEventListener("touchmove", this.__touchmove.bind(this));
        this.target_node.addEventListener("touchend", this.__touchend.bind(this));
    }

    /** disable listener */
    disable() {
        this.enabled = false;
        this.target_node.removeEventListener("touchstart", this.__touchstart);
        this.target_node.removeEventListener("touchmove", this.__touchmove);
        this.target_node.removeEventListener("touchend", this.__touchend);
    }

    /** Touchstart */
    __touchstart(event) {
        event.stopPropagation();
        this.xdown = event.touches[0].clientX;
        this.ydown = event.touches[0].clientY;

        /** reset count of moves in each direction, [left, right, up, down] */
        this.counts = [0, 0, 0, 0];
    }

    /** Touchend */
    __touchend(event) {
        let max_moves = Math.max(...this.counts);
        if (max_moves > 500) { // set this threshold appropriately
            /** swipe happened */
            let index = this.counts.indexOf(max_moves);
            if (index == 0) {
                this.callback("left");
            } else if (index == 1) {
                this.callback("right");
            } else if (index == 2) {
                this.callback("up");
            } else {
                this.callback("down");
            }
        }
    }

    /** Touchmove */
    __touchmove(event) {

        event.stopPropagation();
        if (! this.xdown || ! this.ydown) {
            return;
        }

        let xup = event.touches[0].clientX;
        let yup = event.touches[0].clientY;

        let xdiff = this.xdown - xup;
        let ydiff = this.ydown - yup;

        /** Check x or y has greater distance */
        if (Math.abs(xdiff) > Math.abs(ydiff)) {
            if (xdiff > 0) {
                this.counts[0] += Math.abs(xdiff);
            } else {
                this.counts[1] += Math.abs(xdiff);
            }
        } else {
            if (ydiff > 0) {
                this.counts[2] += Math.abs(ydiff);
            } else {
                this.counts[3] += Math.abs(ydiff);
            }
        }
    }
}

Is this for ES5 or ES6?
@gigawatts I don't recall. The project that used that has reached EOL already and I haven't needed the code since. I suspect at the time I was writing for ES6 but that was over 2 years ago.
1
1.21 gigawatts

Adding to this answer here. This one adds support for mouse events for testing on desktop:

<!--scripts-->
class SwipeEventDispatcher {
    constructor(element, options = {}) {
        this.evtMap = {
            SWIPE_LEFT: [],
            SWIPE_UP: [],
            SWIPE_DOWN: [],
            SWIPE_RIGHT: []
        };

        this.xDown = null;
        this.yDown = null;
        this.element = element;
        this.isMouseDown = false;
        this.listenForMouseEvents = true;
        this.options = Object.assign({ triggerPercent: 0.3 }, options);

        element.addEventListener('touchstart', evt => this.handleTouchStart(evt), false);
        element.addEventListener('touchend', evt => this.handleTouchEnd(evt), false);
        element.addEventListener('mousedown', evt => this.handleMouseDown(evt), false);
        element.addEventListener('mouseup', evt => this.handleMouseUp(evt), false);
    }

    on(evt, cb) {
        this.evtMap[evt].push(cb);
    }

    off(evt, lcb) {
        this.evtMap[evt] = this.evtMap[evt].filter(cb => cb !== lcb);
    }

    trigger(evt, data) {
        this.evtMap[evt].map(handler => handler(data));
    }

    handleTouchStart(evt) {
        this.xDown = evt.touches[0].clientX;
        this.yDown = evt.touches[0].clientY;
    }

    handleMouseDown(evt) {
        if (this.listenForMouseEvents==false) return;
        this.xDown = evt.clientX;
        this.yDown = evt.clientY;
        this.isMouseDown = true;
    }

    handleMouseUp(evt) {
        if (this.isMouseDown == false) return;
        const deltaX = evt.clientX - this.xDown;
        const deltaY = evt.clientY - this.yDown;
        const distMoved = Math.abs(Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : deltaY);
        const activePct = distMoved / this.element.offsetWidth;

        if (activePct > this.options.triggerPercent) {
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                deltaX < 0 ? this.trigger('SWIPE_LEFT') : this.trigger('SWIPE_RIGHT');
            } else {
                deltaY > 0 ? this.trigger('SWIPE_UP') : this.trigger('SWIPE_DOWN');
            }
        }
    }

    handleTouchEnd(evt) {
        const deltaX = evt.changedTouches[0].clientX - this.xDown;
        const deltaY = evt.changedTouches[0].clientY - this.yDown;
        const distMoved = Math.abs(Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : deltaY);
        const activePct = distMoved / this.element.offsetWidth;

        if (activePct > this.options.triggerPercent) {
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                deltaX < 0 ? this.trigger('SWIPE_LEFT') : this.trigger('SWIPE_RIGHT');
            } else {
                deltaY > 0 ? this.trigger('SWIPE_UP') : this.trigger('SWIPE_DOWN');
            }
        }
    }
}

// add a listener on load
window.addEventListener("load", function(event) {
    const dispatcher = new SwipeEventDispatcher(document.body);
    dispatcher.on('SWIPE_RIGHT', () => { console.log('I swiped right!') })
    dispatcher.on('SWIPE_LEFT', () => { console.log('I swiped left!') })
});

This is perfect.
V
Vargr

If you just need swipe, you are better off size wise just using only the part you need. This should work on any touch device.

This is ~450 bytes' after gzip compression, minification, babel etc.

I wrote the below class based on the other answers, it uses percentage moved instead of pixels, and a event dispatcher pattern to hook/unhook things.

Use it like so:

const dispatcher = new SwipeEventDispatcher(myElement);
dispatcher.on('SWIPE_RIGHT', () => { console.log('I swiped right!') })

export class SwipeEventDispatcher { constructor(element, options = {}) { this.evtMap = { SWIPE_LEFT: [], SWIPE_UP: [], SWIPE_DOWN: [], SWIPE_RIGHT: [] }; this.xDown = null; this.yDown = null; this.element = element; this.options = Object.assign({ triggerPercent: 0.3 }, options); element.addEventListener('touchstart', evt => this.handleTouchStart(evt), false); element.addEventListener('touchend', evt => this.handleTouchEnd(evt), false); } on(evt, cb) { this.evtMap[evt].push(cb); } off(evt, lcb) { this.evtMap[evt] = this.evtMap[evt].filter(cb => cb !== lcb); } trigger(evt, data) { this.evtMap[evt].map(handler => handler(data)); } handleTouchStart(evt) { this.xDown = evt.touches[0].clientX; this.yDown = evt.touches[0].clientY; } handleTouchEnd(evt) { const deltaX = evt.changedTouches[0].clientX - this.xDown; const deltaY = evt.changedTouches[0].clientY - this.yDown; const distMoved = Math.abs(Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : deltaY); const activePct = distMoved / this.element.offsetWidth; if (activePct > this.options.triggerPercent) { if (Math.abs(deltaX) > Math.abs(deltaY)) { deltaX < 0 ? this.trigger('SWIPE_LEFT') : this.trigger('SWIPE_RIGHT'); } else { deltaY > 0 ? this.trigger('SWIPE_UP') : this.trigger('SWIPE_DOWN'); } } } } export default SwipeEventDispatcher;


R
Romanna Semenyshyn

I have merged a few of the answers too, mostly the first one and the second with classes, and here is my version:

export default class Swipe {
    constructor(options) {
        this.xDown = null;
        this.yDown = null;

        this.options = options;

        this.handleTouchStart = this.handleTouchStart.bind(this);
        this.handleTouchMove = this.handleTouchMove.bind(this);

        document.addEventListener('touchstart', this.handleTouchStart, false);
        document.addEventListener('touchmove', this.handleTouchMove, false);

    }

    onLeft() {
        this.options.onLeft();
    }

    onRight() {
        this.options.onRight();
    }

    onUp() {
        this.options.onUp();
    }

    onDown() {
        this.options.onDown();
    }

    static getTouches(evt) {
        return evt.touches      // browser API

    }

    handleTouchStart(evt) {
        const firstTouch = Swipe.getTouches(evt)[0];
        this.xDown = firstTouch.clientX;
        this.yDown = firstTouch.clientY;
    }

    handleTouchMove(evt) {
        if ( ! this.xDown || ! this.yDown ) {
            return;
        }

        let xUp = evt.touches[0].clientX;
        let yUp = evt.touches[0].clientY;

        let xDiff = this.xDown - xUp;
        let yDiff = this.yDown - yUp;


        if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
            if ( xDiff > 0 && this.options.onLeft) {
                /* left swipe */
                this.onLeft();
            } else if (this.options.onRight) {
                /* right swipe */
                this.onRight();
            }
        } else {
            if ( yDiff > 0 && this.options.onUp) {
                /* up swipe */
                this.onUp();
            } else if (this.options.onDown){
                /* down swipe */
                this.onDown();
            }
        }

        /* reset values */
        this.xDown = null;
        this.yDown = null;
    }
}

Afterward can use it as the following:

let swiper = new Swipe({
                    onLeft() {
                        console.log('You swiped left.');
                    }
});

It helps to avoid console errors when you want to call only let's say "onLeft" method.


A
Abdur Rehman

Used two:

jQuery mobile: work in most of cases and specially when you are developing applicaion which uses other jQuery plugin then better to use jQuery mobile controls for this. Visit it here: https://www.w3schools.com/jquerymobile/jquerymobile_events_touch.asp

Hammer Time ! one of the best,lightweight and fast javascript based library. Visit it here: https://hammerjs.github.io/


R
Ruben Martinez Jr.

I reworked @givanse's solution to function as a React hook. Input is some optional event listeners, output is a functional ref (needs to be functional so the hook can re-run when/if the ref changes).

Also added in vertical/horizontal swipe threshold param, so that small motions don't accidentally trigger the event listeners, but these can be set to 0 to mimic original answer more closely.

Tip: for best performance, the event listener input functions should be memoized.

function useSwipeDetector({
    // Event listeners.
    onLeftSwipe,
    onRightSwipe,
    onUpSwipe,
    onDownSwipe,

    // Threshold to detect swipe.
    verticalSwipeThreshold = 50,
    horizontalSwipeThreshold = 30,
}) {
    const [domRef, setDomRef] = useState(null);
    const xDown = useRef(null);
    const yDown = useRef(null);

    useEffect(() => {
        if (!domRef) {
            return;
        }

        function handleTouchStart(evt) {
            const [firstTouch] = evt.touches;
            xDown.current = firstTouch.clientX;
            yDown.current = firstTouch.clientY;
        };

        function handleTouchMove(evt) {
            if (!xDown.current || !yDown.current) {
                return;
            }

            const [firstTouch] = evt.touches;
            const xUp = firstTouch.clientX;
            const yUp = firstTouch.clientY;
            const xDiff = xDown.current - xUp;
            const yDiff = yDown.current - yUp;

            if (Math.abs(xDiff) > Math.abs(yDiff)) {/*most significant*/
                if (xDiff > horizontalSwipeThreshold) {
                    if (onRightSwipe) onRightSwipe();
                } else if (xDiff < -horizontalSwipeThreshold) {
                    if (onLeftSwipe) onLeftSwipe();
                }
            } else {
                if (yDiff > verticalSwipeThreshold) {
                    if (onUpSwipe) onUpSwipe();
                } else if (yDiff < -verticalSwipeThreshold) {
                    if (onDownSwipe) onDownSwipe();
                }
            }
        };

        function handleTouchEnd() {
            xDown.current = null;
            yDown.current = null;
        }

        domRef.addEventListener("touchstart", handleTouchStart, false);
        domRef.addEventListener("touchmove", handleTouchMove, false);
        domRef.addEventListener("touchend", handleTouchEnd, false);

        return () => {
            domRef.removeEventListener("touchstart", handleTouchStart);
            domRef.removeEventListener("touchmove", handleTouchMove);
            domRef.removeEventListener("touchend", handleTouchEnd);
        };
    }, [domRef, onLeftSwipe, onRightSwipe, onUpSwipe, onDownSwipe, verticalSwipeThreshold, horizontalSwipeThreshold]);

    return (ref) => setDomRef(ref);
};

Any chance you can add an example usage?
j
jgmjgm

You might have an easier time first implementing it with mouse events to prototype.

There are many answers here, including the top, should be used with caution as they do not consider edge cases especially around bounding boxes.

See:

Touch Events

Mouse Events

You will need to experiment to catch edge cases and behaviours such as the pointer moving outside of the element before ending.

A swipe is a very basic gesture which is a higher level of interface pointer interaction processing roughly sitting between processing raw events and handwriting recognition.

There's no single exact method for detecting a swipe or a fling though virtually all generally follow a basic principle of detecting a motion across an element with a threshold of distance and speed or velocity. You might simply say that if there is a movement across 65% of the screen size in a given direction within a given time then it is a swipe. Exactly where you draw the line and how you calculate it is up to you.

Some might also look at it from the perspective of momentum in a direction and how far off the screen it has been pushed when the element is released. This is clearer with sticky swipes where the element can be dragged and then on release will either bounce back or fly off the screen as if the elastic broke.

It's probably ideal to try to find a gesture library that you can either port or reuse that's commonly used for consistency. Many of the examples here are excessively simplistic, registering a swipe as the slightest touch in any direction.

Android would be the obvious choice though has the opposite problem, it's overly complex.

Many people appear to have misinterpreted the question as any movement in a direction. A swipe is a broad and relatively brief motion overwhelmingly in a single direction (though may be arced and have certain acceleration properties). A fling is similar though intends to casually propel an item away a fair distance under its own momentum.

The two are sufficiently similar that some libraries might only provide fling or swipe, which can be used interchangeably. On a flat screen it's difficult to truly separate the two gestures and generally speaking people are doing both (swiping the physical screen but flinging the UI element displayed on the screen).

You best option is to not do it yourself. There are already a large number of JavaScript libraries for detecting simple gestures.


И
Илья Зеленько

An example of how to use with offset.

// at least 100 px are a swipe // you can use the value relative to screen size: window.innerWidth * .1 const offset = 100; let xDown, yDown window.addEventListener('touchstart', e => { const firstTouch = getTouch(e); xDown = firstTouch.clientX; yDown = firstTouch.clientY; }); window.addEventListener('touchend', e => { if (!xDown || !yDown) { return; } const { clientX: xUp, clientY: yUp } = getTouch(e); const xDiff = xDown - xUp; const yDiff = yDown - yUp; const xDiffAbs = Math.abs(xDown - xUp); const yDiffAbs = Math.abs(yDown - yUp); // at least are a swipe if (Math.max(xDiffAbs, yDiffAbs) < offset ) { return; } if (xDiffAbs > yDiffAbs) { if ( xDiff > 0 ) { console.log('left'); } else { console.log('right'); } } else { if ( yDiff > 0 ) { console.log('up'); } else { console.log('down'); } } }); function getTouch (e) { return e.changedTouches[0] }


Currently using this version. How would I prevent this from firing multiple times if swiped in repetition? I utilize this with the animate feature for a sidescrolling form and when I swipe multiple times, things get a bit screwy and my divs start overlapping in the visible area.
d
dimButTries

I had to write a simple script for a carousel to detect swipe left or right.

I utilised Pointer Events instead of Touch Events.

I hope this is useful to individuals and I welcome any insights to improve my code; I feel rather sheepish to join this thread with significantly superior JS developers.

function getSwipeX({elementId}) {

  this.e               = document.getElementsByClassName(elementId)[0];
  this.initialPosition = 0;
  this.lastPosition    = 0;
  this.threshold       = 200;
  this.diffInPosition  = null;
  this.diffVsThreshold = null;
  this.gestureState    = 0;

  this.getTouchStart = (event) => {
    event.preventDefault();
    if (window.PointerEvent) {
      this.e.setPointerCapture(event.pointerId);
    }
    return this.initalTouchPos = this.getGesturePoint(event);
  }

  this.getTouchMove  = (event) => {
    event.preventDefault();
    return this.lastPosition = this.getGesturePoint(event);
  }

  this.getTouchEnd   = (event) => {
    event.preventDefault();
    if (window.PointerEvent) {
      this.e.releasePointerCapture(event.pointerId);
    }
    this.doSomething();
    this.initialPosition = 0;
  }

  this.getGesturePoint = (event) => {
    this.point = event.pageX
    return this.point;
  }

  this.whatGestureDirection = (event) => {
    this.diffInPosition  = this.initalTouchPos - this.lastPosition;
    this.diffVsThreshold = Math.abs(this.diffInPosition) > this.threshold;
    (Math.sign(this.diffInPosition) > 0) ? this.gestureState = 'L' : (Math.sign(this.diffInPosition) < 0) ? this.gestureState = 'R' : this.gestureState = 'N';
    
    return [this.diffInPosition, this.diffVsThreshold, this.gestureState];
  }

  this.doSomething = (event) => {
    let [gestureDelta,gestureThreshold,gestureDirection] = this.whatGestureDirection();

    // USE THIS TO DEBUG
    console.log(gestureDelta,gestureThreshold,gestureDirection);

    if (gestureThreshold) {
      (gestureDirection == 'L') ? // LEFT ACTION : // RIGHT ACTION
    }
  }

  if (window.PointerEvent) {
    this.e.addEventListener('pointerdown', this.getTouchStart, true);
    this.e.addEventListener('pointermove', this.getTouchMove, true);
    this.e.addEventListener('pointerup', this.getTouchEnd, true);
    this.e.addEventListener('pointercancel', this.getTouchEnd, true);
  }
}

You can call the function using new.

window.addEventListener('load', () => {
  let test = new getSwipeX({
    elementId: 'your_div_here'
  });
})

S
Segebee

I reworked @ruben-martinez answer for using the amazing solution from @givanse for handling swipe events using custom react hooks.

import React, { useEffect, useRef, useState } from "react";

export default function useSwiper() {
  const [domRef, setDomRef] = useState<any>();

  const xDown: React.MutableRefObject<number | null> = useRef(null);
  const yDown: React.MutableRefObject<number | null> = useRef(null);

  useEffect(() => {
if (!domRef) return;

function getTouches(event: React.TouchEvent<HTMLDivElement>) {
  return event.touches;
}

function handleTouchStart(event: any) {
  const firstTouch = getTouches(event)[0];
  xDown.current = firstTouch.clientX;
  yDown.current = firstTouch.clientY;
}

function handleTouchMove(event: React.TouchEvent<HTMLDivElement>) {
  if (!xDown.current || !yDown.current) return;

  const firstTouch = getTouches(event)[0];
  const xUp = firstTouch.clientX;
  const yUp = firstTouch.clientY;

  const xDiff = xDown.current - xUp;
  const yDiff = yDown.current - yUp;

  if (Math.abs(xDiff) > Math.abs(yDiff)) {
    // handle horizontal swipes
    if (xDiff > 0) {
      // we swiped right
      console.log("right");
    } else {
      // we swiped left
      console.log("left");
    }
  } else {
    // handle vertical swipes
    if (yDiff > 0) {
      // we swiped down
      console.log("down");
    } else {
      // we swiped up
      console.log("up");
    }
  }
}

function handleTouchEnd(event: React.TouchEvent<HTMLDivElement>) {
  xDown.current = null;
  yDown.current = null;
}


  domRef.addEventListener("touchstart", handleTouchStart, false);
  domRef.addEventListener("touchmove", handleTouchMove, false);
  domRef.addEventListener("touchend", handleTouchEnd, false);

return () => {
    domRef.removeEventListener("touchstart", handleTouchStart, false);
    domRef.removeEventListener("touchmove", handleTouchMove, false);
    domRef.removeEventListener("touchend", handleTouchEnd, false);
};
  }, [domRef]);

  return (ref: any) => setDomRef(ref);
}

My major challenge with implementing his answer was not knowing how to bind the swipe element's ref to the ref from the custom hook.

Basically, what is happening is that we return a function from the custom hook. This function would allow us pass in a ref from the element we want to listen to swipe actions on. The custom hook on receipt of the ref then updates the hook state with the element's ref which triggers a re render so we have the actual element!

This functional ref style also allows us to use the hook for multiple elements. As shown below, I wanted to use it for a list of items to enable swipe to delete :)

import useSwiper from "./hooks/useSwipe";

const EntryCard = ({ entry, godMode, reload }: EntryProps) => {
const swiperRef = useSwiper();

const handleEntryClick =
(entry: Entry) => async (event: React.MouseEvent<HTMLDivElement>) => {
  if (!godMode) return;

  try {
    reload((state) => !state);
  } catch (err) {
    console.log("Error deleting entry: ", err);
  }
};

return (
  <div className="item" onClick={handleEntryClick(entry)} ref={swiperRef}>
    <div className="username">{entry.userName}</div>
    <div className="score">{entry.weekScore}</div>
  </div>
 );
};

PS: You can pass in functions to your hook to receive the swipe values. Thank YOU :) Vote if you like :)


j
javad hemati

handle by touchStart and touchEnd :

var handleSwipe = function(elem,callbackOnRight, callbackOnLeft, callbackOnDown, 
      callbackOnUp) => {

        elem.ontouchstart = handleTouchStart;
        elem.ontouchend = handleTouchEnd;

        var xDown = null;
        var yDown = null;

        function getTouches(evt) {
            return evt.touches ||             // browser API
                evt.originalEvent.touches; // jQuery
        }

        function handleTouchStart(evt) {
            const firstTouch = getTouches(evt)[0];
            xDown = firstTouch.clientX;
            yDown = firstTouch.clientY;
        };

        function handleTouchEnd(evt) {
            if (!xDown || !yDown) {
                return;
            }

            var xUp = evt.changedTouches[0].clientX;
            var yUp = evt.changedTouches[0].clientY;

            var xDiff = xDown - xUp;
            var yDiff = yDown - yUp;
            var minDif = 30;

            console.log(`xDiff:${xDiff}, yDiff:${yDiff}`);

            if (Math.abs(xDiff) > Math.abs(yDiff)) {
                if (xDiff > minDif) {
                    if (callbackOnLeft)
                        callbackOnLeft();
                } else if (xDiff < -1 * minDif){
                    if (callbackOnRight)
                        callbackOnRight();
                }
            } else {
                if (yDiff > minDif) {
                    if (callbackOnDown)
                        callbackOnDown();
                } else if (yDiff < -1* minDif){
                    if (callbackOnUp)
                        callbackOnUp();
                }
            }
            
            xDown = null;
            yDown = null;
        };
    }

z
zurfyx

You can listen to touchstart and touchend events and compute the direction and force based on the event data (Codepen):

let start = null; document.addEventListener('touchstart', e => { const touch = e.changedTouches[0]; start = [touch.clientX, touch.clientY]; }); document.addEventListener('touchend', e => { const touch = e.changedTouches[0]; const end = [touch.clientX, touch.clientY]; document.body.innerText = `${end[0] - start[0]},${end[1] - start[1]}`; }); Swipe here

Or you can build a more ergonomic API around this same concept (Codepen):

const removeListener = addSwipeRightListener(document, (force, e) => {
  console.info('Swiped right with force: ' + force);
});
// removeListener()

// swipe.js const { addSwipeLeftListener, addSwipeRightListener, addSwipeUpListener, addSwipeDownListener, } = (function() { // const elements = new WeakMap(); function readTouch(e) { const touch = e.changedTouches[0]; if (touch == undefined) { return null; } return [touch.clientX, touch.clientY]; } function addListener(element, cb) { let elementValues = elements.get(element); if (elementValues === undefined) { const listeners = new Set(); const handleTouchstart = e => { elementValues.start = readTouch(e); }; const handleTouchend = e => { const start = elementValues.start; if (start === null) { return; } const end = readTouch(e); for (const listener of listeners) { listener([end[0] - start[0], end[1] - start[1]], e); } }; element.addEventListener('touchstart', handleTouchstart); element.addEventListener('touchend', handleTouchend); elementValues = { start: null, listeners, handleTouchstart, handleTouchend, }; elements.set(element, elementValues); } elementValues.listeners.add(cb); return () => deleteListener(element, cb); } function deleteListener(element, cb) { const elementValues = elements.get(element); const listeners = elementValues.listeners; listeners.delete(cb); if (listeners.size === 0) { elements.delete(element); element.removeEventListener('touchstart', elementValues.handleTouchstart); element.removeEventListener('touchend', elementValues.handleTouchend); } } function addSwipeLeftListener(element, cb) { return addListener(element, (force, e) => { const [x, y] = force; if (x < 0 && -x > Math.abs(y)) { cb(x, e); } }); } function addSwipeRightListener(element, cb) { return addListener(element, (force, e) => { const [x, y] = force; if (x > 0 && x > Math.abs(y)) { cb(x, e); } }); } function addSwipeUpListener(element, cb) { return addListener(element, (force, e) => { const [x, y] = force; if (y < 0 && -y > Math.abs(x)) { cb(x, e); } }); } function addSwipeDownListener(element, cb) { return addListener(element, (force, e) => { const [x, y] = force; if (y > 0 && y > Math.abs(x)) { cb(x, e); } }); } return { addSwipeLeftListener, addSwipeRightListener, addSwipeUpListener, addSwipeDownListener, } })(); // app.js function print(direction, force) { document.querySelector('#direction').innerText = direction; document.querySelector('#data').innerText = force; } addSwipeLeftListener(document, (force, e) => { print('left', force); }); addSwipeRightListener(document, (force, e) => { print('right', force); }); addSwipeUpListener(document, (force, e) => { print('up', force); }); addSwipeDownListener(document, (force, e) => { print('down', force); });

Swipe

Force (px):