441

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.

5
  • 6
    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 Nov 4, 2016 at 0:01
  • There is an event: "touchmove"
    – Clay
    May 23, 2020 at 12:19
  • 1
    @Clay that one still does not work in Safari so no iPhone.
    – Jakuje
    Jun 12, 2020 at 20:30
  • @Jakuje "Can I Use" website says that Safari on iOS supports touch events, so iPhones yes. Dec 18, 2022 at 7:56
  • latest versions of iOS have native ootb handling of left-right swipe gestures over an HTML5 video element; the gestures will seek a video with some dynamic animated UI popup to indicate the current position in the video; so probably this case should be detected explicitly unless it's intended to override this behaviour.
    – ccpizza
    Feb 23, 2023 at 10:59

28 Answers 28

544

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.

16
  • 2
    Looks cool and simple, any idea what is the support for this events touchstart, touchmove ?
    – d.raev
    Jun 5, 2014 at 7:46
  • 1
    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.
    – Codebeat
    Apr 22, 2015 at 12:12
  • 1
    Damn. Topic is closed so cannot add my answer!
    – Codebeat
    Apr 22, 2015 at 12:13
  • 6
    This works great, but left/right and up/down are backwards. Feb 16, 2016 at 2:44
  • 7
    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.
    – Jan Derk
    May 21, 2017 at 20:16
274

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.

5
  • 7
    Lol this is so simple and even allows to specify a "travel distance".
    – codepleb
    May 1, 2021 at 7:35
  • 7
    Best answer by far.. it's a shame it doesn't have more upvotes.. Jun 24, 2021 at 20:08
  • 3
    @MattiaRasulo maybe need to add up and down swipe
    – Mystogan
    Nov 1, 2021 at 5:27
  • 2
    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 Jan 31, 2022 at 20:10
  • 5
    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. Mar 17, 2022 at 16:22
63

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

5
  • I came here because pure-swipe was not working for me on MOBILE
    – StefanBob
    May 4, 2018 at 15:28
  • @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 Jan 10, 2019 at 15:15
  • 2
    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)
    – collimarco
    Nov 26, 2019 at 22:24
  • 3
    Hammer.js doesn't seem to be maintained anymore
    – AlexandreS
    Nov 19, 2020 at 14:07
  • 1
    @JohnDoherty would you extend it so it also works on desktop(non-touch) devices. Jul 6, 2022 at 18:36
47

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();
7
  • 8
    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 Oct 14, 2016 at 12:02
  • 3
    I just removed .bind(this); and it worked gracefully. thank you @nicholas_r Jul 29, 2017 at 14:58
  • Part get the element yourself I just remove '#' in document.getElementById('my-element') and it worked good. Thank you @Marwelln :)
    – Blue Tram
    Mar 25, 2019 at 6:01
  • 6
    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
    – TetraDev
    Apr 19, 2019 at 1:41
  • 1
    call run() twice and you get a nasty memory leak
    – Mat
    Apr 19, 2019 at 12:09
20

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
5
  • originalEvent is a JQuery property. It does not even exist in pure Javascript.
    – Jan Derk
    May 21, 2017 at 20:19
  • 1
    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.
    – nashcheez
    May 21, 2017 at 20:35
  • 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.
    – Jan Derk
    May 22, 2017 at 8:34
  • 1
    Yeah I researched a bit and realized you were right about the availability of jquery enabling event.originalEvent. I will update my answer. Thanks! :)
    – nashcheez
    May 22, 2017 at 11:06
  • 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. Sep 3, 2020 at 20:22
16

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.

1
  • 31
    I believe it's touchstart, touchmove, touchcancel, and touchend that one would work with, not mousedown or mouseup.
    – Volomike
    Jan 16, 2011 at 21:42
13

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;
    }
};
1
  • Added if ( ! xDown || ! yDown || e.touches.length === 2 ) { for horizontal pinch zoom
    – Jack
    Jun 3, 2023 at 22:25
12

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

Example

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

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

7

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;
}
0
6

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;
}
5

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!') })
});
1
  • This is perfect.
    – Dgloria
    Jul 26, 2021 at 11:24
4

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

1
  • I also needed to add: $.event.special.swipe.scrollSupressionThreshold = 8; but you put me in the right direction! Thank you!
    – Philip G
    Dec 26, 2014 at 11:39
4

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);
            }
        }
    }
}
2
  • Is this for ES5 or ES6? Dec 31, 2019 at 1:44
  • @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. Jan 1, 2020 at 5:02
3

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;

3

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.

2

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/

2

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);
};
1
  • 1
    Any chance you can add an example usage?
    – Vivi
    Feb 19, 2021 at 1:45
2

On top of what was suggested here, I would keep track of finger numbers, because if you touch with two fingers at the same time, it will pick up the X position without the ~swipe~ motion which leads to a weird behavior, and also, you could want to set a "distance" minimum so the user won't trigger the swipe by mistake when touch through your website or application.

//Swipe 
let touchstartX = 0
let touchendX = 0
let fingerCount = 0
    
const checkDirection = () => {

  const distance = 50 //Minimum distance for the swipe to work

  //left
  if (touchendX < touchstartX && (touchstartX - touchendX) > distance ){

  //Do something cool
   
  } 
  //right
  if (touchendX > touchstartX && (touchendX - touchstartX) > distance){

    //Do something cooler
}

document.addEventListener('touchstart', e => {

    fingerCount = e.touches.length
    touchstartX = e.changedTouches[0].clientX  
     
})

document.addEventListener('touchend', e => {

    touchendX = e.changedTouches[0].clientX
    if(fingerCount === 1){ 
        checkDirection() 
    }

})
1

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:

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.

0

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 <offset> 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]
}

1
  • 1
    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.
    – NMALM
    Apr 10, 2020 at 19:26
0

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'
  });
})
0

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 :)

0

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;
        };
    }
0

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() {
  // <element, {listeners: [...], handleTouchstart, handleTouchend}>
  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);
});
<h1>Swipe <span id="direction"></span></h1>
Force (px): <span id="data"></span>

0

This is JQuery. Ideally you want to skip the swipe action if the swipe wasn't significant enough.

$('.slider')
    .off('touchstart touchend swipedaction')
    .on('touchstart', function (e) {
        //Set the starting point directly on self
        $(this).touchstartX = e.changedTouches[0].screenX;
    })
    .on('touchend', function (e) {
        //Set the end point directly on self
        let $self = $(this);
        $self.touchendX = e.changedTouches[0].screenX;

        // Swipe more than 50px, else don't action it.
        if (Math.abs($self.touchendX - $self.touchstartX) > 50) {
            if ($self.touchendX < $self.touchstartX) {
                $self.trigger('swipedaction', ['left']);
            } else {
                $self.trigger('swipedaction', ['right']);
            }
        } else {
            e.stopPropagation();
        }
    })
    .on('swipedaction', function(e, direction) {
        if (direction === 'left') {
            // Swiped left, move right
        } else {
            // Swiped right, move left
        }
    });
2
  • I encountered an undefined error on e.changedTouches[0], but changing it to e.originalEvent.changedTouches[0] worked (stackoverflow.com/a/46790285/6247322). Also, touchstartX does not get stored here $(this).touchstartX = e.changedTouches[0].screenX;. I had to create a variable outside of the listener (i.e. let $self), then within .on('touchstart', function(e) { I set the variable $self = $(this); $self.touchStartX = e.originalEvent.changedTouches[0].screenX;
    – limciana
    May 11, 2023 at 2:33
  • 1
    If that is the case for you, you can also replace $(this) and $self with the selector $('.slider') as well. It stores the variables on the JQuery instance of the element. WIth regards to the undefined error you are getting, it is probably the best then to check for both e.changedTouches and e.originalEvent.changedTouches before using it.
    – Pierre
    May 12, 2023 at 6:32
-1

Function checks both horizontal and vertical direction to determine which swipe was longer to prevent executing 2 instructions, because it's impossible to make a perfect one-directioned swipe. A swipe always has deviation on X and Y.

let touchstartX = 0;
let touchendX = 0;
let touchstartY = 0;
let touchendY = 0;   

function checkDirection() {
    let difX = touchstartX - touchendX;
    let difY = touchstartY - touchendY;
if (Math.abs(difX) > Math.abs(difY)) {
    if (touchendX < touchstartX) {/*left*/}
    if (touchendX > touchstartX) {/*right*/}
} else {
    if (touchendY < touchstartY) {/*up*/}
    if (touchendY > touchstartY) {/*down*/}
}
};
document.addEventListener('touchstart', e => {
    e.preventDefault();
    touchstartX = e.changedTouches[0].screenX;
    touchstartY = e.changedTouches[0].screenY;
});

document.addEventListener('touchend', e => {
    e.preventDefault();
    touchendX = e.changedTouches[0].screenX;
    touchendY = e.changedTouches[0].screenY;
    checkDirection();
});
-1
class Carousel {
    constructor(carouselWrapper, carouselItems, carouselPrev, carouselNext, dotContainer) {
        this.carouselWrapper = document.querySelectorAll(carouselWrapper);
        this.carouselItems = Array.from(document.querySelectorAll(carouselItems));
        this.carouselPrev = document.querySelector(carouselPrev);
        this.carouselNext = document.querySelector(carouselNext);
        this.dotContainer = document.querySelector(dotContainer);
        this.currentItem = 0;
        this.maxItem = this.carouselItems.length;

        this.isDragging = false;
        this.startPos = 0;
        this.currentTranslate = 0;
        this.prevTranslate = 0;

        this.#init();
    }

    #init() {
        document.addEventListener('keydown', this.#keyBoardHandler.bind(this));

        this.carouselPrev.addEventListener('click', this.#prevSlide.bind(this));
        this.carouselNext.addEventListener('click', this.#nextSlide.bind(this));
        this.dotContainer.addEventListener('click', this.#dotHandler.bind(this))

        this.#createDots();
        this.#gotoSlide(0)
        this.#activeDots(0)

        this.#touchHandler();
        this.#disableoncontextmenu();

    }

    #touchHandler() {
        this.carouselItems.forEach((slide, index) => {
            const img = slide.querySelector('.carousel__bgimg');
            if(!img) return;
            
            img.addEventListener('dragstart', (e) => e.preventDefault());

            slide.addEventListener('touchstart', this.#touchStart.bind(this));
            slide.addEventListener('touchend', this.#touchEnd.bind(this));
            slide.addEventListener('touchmove', this.#touchMove.bind(this));

        });
    }

    #touchStart() {
        this.isDragging = true;
        this.startPos = this.#getpositionX(event);
    }

    #touchMove() {
        if(this.isDragging) {
            const currentPosition = this.#getpositionX(event);
            this.currentTranslate = this.prevTranslate + currentPosition - this.startPos;
        }
    }

    #touchEnd() {
        this.isDragging = false;
        const movedBy = this.currentTranslate - this.prevTranslate;
        if(movedBy < -100) {
            this.#nextSlide();
        };

        if(movedBy > 100) {
            this.#prevSlide();
        };

    }

    #getpositionX(event) {
        return event.type.includes('mouse') ? event.pageX : event.touches[0].clientX;
    }

    #createDots() {
        this.carouselItems.forEach((_, i) => {
            this.dotContainer.insertAdjacentHTML('beforeend', `<div class="bullet" data-slide="${i}"></div>`)
        });
    }

    #activeDots(slide) {
        document.querySelectorAll('.bullet').forEach(function(dot) {
            dot.classList.remove('active');
        });

        document.querySelector(`.bullet[data-slide="${slide}"]`)
            .classList.add('active');
    }

    #gotoSlide(slide) {
        this.carouselWrapper.forEach((s, i) => {
            s.style.transform = `translate3d(${100 * (i - slide)}%, 0px, 0px)`;
        });
    }

    #prevSlide() {
        if(this.currentItem === 0) {
            this.currentItem = this.maxItem - 1;
        } else {
            this.currentItem--;
        };
        this.#gotoSlide(this.currentItem);
        this.#activeDots(this.currentItem);
    }

    #nextSlide() {
        if(this.currentItem === this.maxItem -1) {
            this.currentItem = 0;
        } else {
            this.currentItem++;
        };
        this.#gotoSlide(this.currentItem);
        this.#activeDots(this.currentItem);
    }

    #dotHandler(e) {
        if(e.target.classList.contains('bullet')) {
            const { slide } = e.target.dataset;
            this.#gotoSlide(slide);
            this.#activeDots(slide);
        }
    }

    #keyBoardHandler(e) {
        if(e.keyCode === 39) this.#nextSlide();
        e.keyCode === 37 && this.#prevSlide();
    }

    #disableoncontextmenu() {
        this.carouselWrapper.forEach(function(item) {
            item.oncontextmenu = function(event) {
                event.preventDefault()
                event.stopPropagation()
                return false
            }
        });
    }
}

document.addEventListener('DOMContentLoaded', function() {
    const slider = new Carousel(
        '.carousel__wrapper',
        '.carousel__item',
        '.prev-ctrl',
        '.next-ctrl',
        '.dots',
        );
});

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.