Skip to content

Instantly share code, notes, and snippets.

@SleepWalker
Created September 30, 2015 04:59
Show Gist options
  • Save SleepWalker/da5636b1abcbaff48c4d to your computer and use it in GitHub Desktop.
Save SleepWalker/da5636b1abcbaff48c4d to your computer and use it in GitHub Desktop.
A simple swipe detection on vanilla js
var touchstartX = 0;
var touchstartY = 0;
var touchendX = 0;
var touchendY = 0;
var gesuredZone = document.getElementById('gesuredZone');
gesuredZone.addEventListener('touchstart', function(event) {
touchstartX = event.screenX;
touchstartY = event.screenY;
}, false);
gesuredZone.addEventListener('touchend', function(event) {
touchendX = event.screenX;
touchendY = event.screenY;
handleGesure();
}, false);
function handleGesure() {
var swiped = 'swiped: ';
if (touchendX < touchstartX) {
alert(swiped + 'left!');
}
if (touchendX > touchstartX) {
alert(swiped + 'right!');
}
if (touchendY < touchstartY) {
alert(swiped + 'down!');
}
if (touchendY > touchstartY) {
alert(swiped + 'left!');
}
if (touchendY == touchstartY) {
alert('tap!');
}
}
@c7x43t
Copy link

c7x43t commented May 3, 2018

Small modification of @stephenjude using angles (45° for each of the 8 directions) to determine the direction and a treshold of 1px or 1% of page width:

let pageWidth = window.innerWidth || document.body.clientWidth;
let treshold = Math.max(1,Math.floor(0.01 * (pageWidth)));
let touchstartX = 0;
let touchstartY = 0;
let touchendX = 0;
let touchendY = 0;

const limit = Math.tan(45 * 1.5 / 180 * Math.PI);
const gestureZone = document.getElementById('modalContent');

gestureZone.addEventListener('touchstart', function(event) {
    touchstartX = event.changedTouches[0].screenX;
    touchstartY = event.changedTouches[0].screenY;
}, false);

gestureZone.addEventListener('touchend', function(event) {
    touchendX = event.changedTouches[0].screenX;
    touchendY = event.changedTouches[0].screenY;
    handleGesture(event);
}, false);

function handleGesture(e) {
    let x = touchendX - touchstartX;
    let y = touchendY - touchstartY;
    let xy = Math.abs(x / y);
    let yx = Math.abs(y / x);
    if (Math.abs(x) > treshold || Math.abs(y) > treshold) {
        if (yx <= limit) {
            if (x < 0) {
                console.log("left");
            } else {
                console.log("right");
            }
        }
        if (xy <= limit) {
            if (y < 0) {
                console.log("top");
            } else {
                console.log("bottom");
            }
        }
    } else {
        console.log("tap");
    }
}

@artgolwebdev
Copy link

Like !

@behnammodi
Copy link

i add this script to npm

npm link:
https://www.npmjs.com/package/xwiper

github link:
https://github.com/uxitten/xwiper

@adsoft-solutions
Copy link

Hello , actually won't work on some Android Chrome newer versions... If someone could add browser interogation for Pointer Events and a solution for the it , would be our hero !

@alexPalita
Copy link

alexPalita commented Sep 6, 2018

Idk... I tried to modify uxitten approach with Pointer Events ... If someone could write a more elegant version of this with pinch and multiple taps would be awesome ...

class Xwiper {
    constructor(element) {
        this.element = null;
        this.touchStartX = 0;
        this.touchStartY = 0;
        this.touchEndX = 0;
        this.touchEndY = 0;
        this.touchMovedX = 0;
        this.touchMovedY = 0;
        this.sensitive = 5;
        this.onSwipeLeftAgent = null;
        this.onSwipeRightAgent = null;
        this.onSwipeUpAgent = null;
        this.onSwipeDownAgent = null;
        this.onTapAgent = null;
        this.onTouchStart = this.onTouchStart.bind(this);
        this.onTouchEnd = this.onTouchEnd.bind(this);
        this.onPointerStart = this.onPointerStart.bind(this);
        this.onPointerMoved = this.onPointerMoved.bind(this);
        this.onPointerEnd = this.onPointerEnd.bind(this);
        this.onSwipeLeft = this.onSwipeLeft.bind(this);
        this.onSwipeRight = this.onSwipeRight.bind(this);
        this.onSwipeUp = this.onSwipeUp.bind(this);
        this.onSwipeDown = this.onSwipeDown.bind(this);
        this.onTap = this.onTap.bind(this);
        this.destroy = this.destroy.bind(this);
        this.handleGesture = this.handleGesture.bind(this);

        this.element = document.querySelector(element);
		if (window.PointerEvent) {
			this.element.addEventListener( 'pointerdown', this.onPointerStart,false);

			this.element.addEventListener( 'pointermove', this.onPointerMoved,false);
				
			this.element.addEventListener( 'pointercancel', this.onPointerEnd,false);
		} else {
			this.element.addEventListener('touchstart', this.onTouchStart ,false);
			this.element.addEventListener('touchend', this.onTouchEnd, false);	
		}
    }

    onTouchStart(event) {
        this.touchStartX = event.changedTouches[0].screenX;
        this.touchStartY = event.changedTouches[0].screenY;
    }

    onTouchEnd(event) {
        this.touchEndX = event.changedTouches[0].screenX;
        this.touchEndY = event.changedTouches[0].screenY;
        this.handleGesture();
    }

    onPointerStart(event) {
		switch ( event.pointerType ) {
			case 'mouse':
				//add code
			break;

			case 'touch':
				this.touchStartX = event.screenX;
				this.touchStartY = event.screenY;
			break;
			case 'pen':
				//add code
			break;
			default:
				//add code
			break;
			}
    }

    onPointerMoved(event) {
		switch ( event.pointerType ) {
			case 'mouse':
				//add code
			break;
			case 'touch':
				this.touchMovedX = event.screenX;
				this.touchMovedY = event.screenY;
			break;
			case 'pen':
				//add code
			break;
			default:
				//add code
			break;
		}
    }
    onPointerEnd(event) {
		switch ( event.pointerType ) {
			case 'mouse':
				//add code
			break;

			case 'touch':
				this.touchEndX = this.touchMovedX;
				this.touchEndY = this.touchMovedY;
			break;

			case 'pen':
				//add code
			break;

			default:
				//add code
			break;
		}
        this.handleGesture();
    }

    onSwipeLeft(func) {
        this.onSwipeLeftAgent = func;
    }
    onSwipeRight(func) {
        this.onSwipeRightAgent = func;
    }
    onSwipeUp(func) {
        this.onSwipeUpAgent = func;
    }
    onSwipeDown(func) {
        this.onSwipeDownAgent = func;
    }
    onTap(func) {
        this.onTapAgent = func;
    }

    destroy() {
        this.element.removeEventListener('touchstart', this.onTouchStart);
        this.element.removeEventListener('touchend', this.onTouchEnd);
        this.element.removeEventListener('pointerstart', this.onTouchStart);
        this.element.removeEventListener('pointermove', this.onTouchMove);
        this.element.removeEventListener('touchover', this.onTouchEnd);
    }

	handleGesture() {
        /**
         * swiped left
         */
        if (this.touchEndX + this.sensitive < this.touchStartX) {
            this.onSwipeLeftAgent &&
                this.onSwipeLeftAgent();
            return 'swiped left';
        }

        /**
         * swiped right
         */
        if (this.touchEndX - this.sensitive > this.touchStartX) {
            this.onSwipeRightAgent &&
                this.onSwipeRightAgent();
            return 'swiped right';
        }

        /**
         * swiped up
         */
        if (this.touchEndY + this.sensitive < this.touchStartY) {
            this.onSwipeUpAgent &&
                this.onSwipeUpAgent();
            return 'swiped up';
        }

        /**
         * swiped down
         */
        if (this.touchEndY - this.sensitive > this.touchStartY) {
            this.onSwipeDownAgent &&
                this.onSwipeDownAgent();
            return 'swiped down';
        }

        /**
         * tap
         */
        if (this.touchEndY === this.touchStartY) {
            this.onTapAgent &&
                this.onTapAgent();
            return 'tap';
        }
    }
}

@hperrin
Copy link

hperrin commented Sep 7, 2018

I took a shot at improving this too. I added multiple listeners support, long press, mouse support, velocity threshold, and customization. It's on NPM and GitHub:

https://www.npmjs.com/package/tinygesture
https://github.com/sciactive/tinygesture

Here's a demo:
https://sciactive.github.io/tinygesture/

Thank you @SleepWalker, @uxitten, @c7x43t.

@javierlog08
Copy link

Thank you so much.
Pretty usefull !~

@tlacaelelrl
Copy link

tlacaelelrl commented Apr 25, 2019

Here I leave you some code I use, some of it was initially based on the code listed here, then modified to adjust to my needs and new events.

TlakDev.EventHandler.prototype._defaults = {
	touch: {
		x: 0,
		y: 0
	},
	touchEnd: {
		x: 0,
		y: 0
	}
}
TlakDev.EventHandler.prototype.sendTouchEvent = function (event, element) {
	let t = this;
	switch(true){
		case ((Math.abs(t.touch.x) - Math.abs(t.touchEnd.x)) === 0) && ((Math.abs(t.touch.y) - Math.abs(t.touchEnd.y)) === 0):
			event.direction = "tap";
		break;
		case Math.abs(t.touch.x - t.touchEnd.x) > Math.abs(t.touch.y - t.touchEnd.y):
			/*left/right*/
			if(t.touch.x > t.touchEnd.x){
				event.direction = "left";
			} else {
				event.direction = "right";
			}
		break;
		default:
			/*up/down*/
			if(t.touch.y < t.touchEnd.y){
				event.direction = "down";
			} else {
				event.direction = "up";
			}
		break;
	}
	switch(true){
		case ((Math.abs(t.touch.x) - Math.abs(t.touchEnd.x)) === 0) && ((Math.abs(t.touch.y) - Math.abs(t.touchEnd.y)) === 0):
			event.directionPrecision = "tap";
		break;
		case t.touch.x > t.touchEnd.x:
			/*left*/
			if(t.touch.y > t.touchEnd.y){
				event.directionPrecision = "upLeft";
			} else {
				event.directionPrecision = "downLeft";
			}
		break;
		default:
			/*right*/
			if(t.touch.y > t.touchEnd.y){
				event.directionPrecision = "upRight";
			} else {
				event.directionPrecision = "downRight";
			}
		break;
	}
	/*
	 * Do whatever you need with the event/element
	 * The event contains 2 properties
	 * 1. direction  = tap|left|right|up|down
	 * 2. directionPrecision tap|upRight|upLeft|downRight|downLeft
	*/
}
TlakDev.EventHandler.prototype.setTouch = function (event) {
	let e = {};
	if(event.screenY){
		e.x = event.screenX;
		e.y = event.screenY;
	} else if(typeof event.touches !== "undefined" && event.touches.length > 0){
		e.x = event.touches[0].pageX;
		e.y = event.touches[0].pageY;
	} else if(typeof event.changedTouches !== "undefined" && event.changedTouches.length > 0) {
		e.x = event.changedTouches[0].pageX;
		e.y = event.changedTouches[0].pageY;
	} else {
		e.x = 0;
		e.y = 0;
	}
	return e;
}
/*
@param nodes is an array of javascript elements to which apply the events
*/
TlakDev.EventHandler.prototype.handleSwipes = function (nodes) {
	let t = this;
	nodes.forEach(function(element){
		element.addEventListener("touchstart", function (event) {
			let e = t.setTouch(event);
			t.touch = e;
		});
		element.addEventListener("touchend", function (event) {
			let e = t.setTouch(event);
			t.touchEnd = e;
			t.sendTouchEvent(event, element);
		});
	});
}

@evrenakar
Copy link

evrenakar commented May 8, 2019

Function that only listen the y down event.

`
var swipe = document.getElementById('swipe');

  var touchstartY = 0;
  var touchendY = 0;

  swipe.addEventListener('touchstart', function(event) {
    touchstartY = event.changedTouches[0].screenY;
  }, false);

  swipe.addEventListener('touchend', function(event) {
    touchendY = event.changedTouches[0].screenY;
      handleSwipe();
  }, false); 

  function handleSwipe() {
      var swiped = 'swiped: ';
      if (touchendY > touchstartY) {
          alert(swiped + 'down!');
      }
  }

`

@karol2001965
Copy link

Thanks a lot ;)

@jonaed1230
Copy link

Thanks a lot it saved my day.

@AEsmerio
Copy link

@IanRr thanks for that correction. @mocon thanks for tweak
It works fine now

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

const gestureZone = document.getElementById('modalContent');

gestureZone.addEventListener('touchstart', function(event) {
    touchstartX = event.changedTouches[0].screenX;
    touchstartY = event.changedTouches[0].screenY;
}, false);

gestureZone.addEventListener('touchend', function(event) {
    touchendX = event.changedTouches[0].screenX;
    touchendY = event.changedTouches[0].screenY;
    handleGesture();
}, false); 

function handleGesture() {
    if (touchendX < touchstartX) {
        console.log('Swiped left');
    }
    
    if (touchendX > touchstartX) {
        console.log('Swiped right');
    }
    
    if (touchendY < touchstartY) {
        console.log('Swiped up');
    }
    
    if (touchendY > touchstartY) {
       console.log('Swiped down');
    }
    
    if (touchendY === touchstartY) {
       console.log('Tap');
    }
}

THanks, man. It was really helpfull

@tryhardest
Copy link

tryhardest commented Jun 22, 2020

This is starting to look like one of the leading script threads on vanillaJS touch events. Seems like Hammer, Zing and Interactjs (pointer events) libs (interact excluded) are maybe become a bit obsolete except for drag, drop, and pointer support. Does anyone have any flushed out changes they have made since (as the thread goes back a few years)? Would be cool to keep this thread up to date with any changes or learnings. Anyone using this extensively with event listeners on touchMove? @evrenakar @tlacaelelrl @hperrin

@vwebtech
Copy link

vwebtech commented Jul 8, 2020

Everything is wrong .

if you swipe UP , Down , or Left Right it will trigger the action.

because its not checking the direction.

Its just checking the one point to another point on screen, and it can not determine the direction.
i will share new code once i fix this.
Thanks.

@Mapiac
Copy link

Mapiac commented Jul 21, 2020

What did you change @vwebtech ?

@sekoyo
Copy link

sekoyo commented Jul 21, 2020

I guess he means it will trigger multiple conditions, you can check which direction has the most bias:

let startX = 0;
let startY = 0;

function handleTouchStart(e) {
  startX = e.changedTouches[0].screenX;
  startY = e.changedTouches[0].screenY;
}

function handleTouchEnd(e) {
  const diffX = e.changedTouches[0].screenX - startX;
  const diffY = e.changedTouches[0].screenY - startY;
  const ratioX = Math.abs(diffX / diffY);
  const ratioY = Math.abs(diffY / diffX);
  const absDiff = Math.abs(ratioX > ratioY ? diffX : diffY);

  // Ignore small movements.
  if (absDiff < 30) {
    return;
  }

  if (ratioX > ratioY) {
    if (diffX >= 0) {
      console.log('right swipe');
    } else {
      console.log('left swipe');
    }
  } else {
    if (diffY >= 0) {
      console.log('down swipe');
    } else {
      console.log('up swipe');
    }
  }
}

@luc-sauvage
Copy link

hello people, the 0 in the changedTouches property stands for the number of touches i guess, meaning the number of fingers needed to detect the event? please help, i cannot find proper answers online (Or at least answers i understand...)...
Thanks

@hkkcngz
Copy link

hkkcngz commented Aug 18, 2020

I guess he means it will trigger multiple conditions, you can check which direction has the most bias:

let startX = 0;
let startY = 0;

function handleTouchStart(e) {
  startX = e.changedTouches[0].screenX;
  startY = e.changedTouches[0].screenY;
}

function handleTouchEnd(e) {
  const diffX = e.changedTouches[0].screenX - startX;
  const diffY = e.changedTouches[0].screenY - startY;
  const ratioX = Math.abs(diffX / diffY);
  const ratioY = Math.abs(diffY / diffX);
  const absDiff = Math.abs(ratioX > ratioY ? diffX : diffY);

  // Ignore small movements.
  if (absDiff < 30) {
    return;
  }

  if (ratioX > ratioY) {
    if (diffX >= 0) {
      console.log('right swipe');
    } else {
      console.log('left swipe');
    }
  } else {
    if (diffY >= 0) {
      console.log('down swipe');
    } else {
      console.log('up swipe');
    }
  }
}

hello, I tried to use your answer and make simple func for that. Also want to make it for only swipe left. Any help?
I tried this and error.

function swipe(e) {
handleTouchStart(e);
handleTouchEnd(e);
}
let e = document.querySelector('.nbr');

swipe(e);

@c4benn
Copy link

c4benn commented Nov 23, 2020

I have a quite lengthy solution that handles touchstart, touchmove, and touchend events alone. ~100 lines of code (minus comments).
This solution also returns the amount of pixels swiped, useful for creating a carousel or a swipeable component.

The code is below, you can modify it and get rid of what you don't need.

//to check that it's the right direction.
    const log = x => console.log(x)

class RootSwipeable {
    //offset is amount of pixels swiped to be considered a direction.
    //else multiple directions might trigger at once.
    constructor({ offset }) { 
        this.offset = offset
        this.previous = { x: null, y: null } 
    }

    updatePrevious(e) {
        this.previous.x =  e.changedTouches[0].screenX
        this.previous.y =  e.changedTouches[0].screenY
    }

    init(e) {
         !this.previous.x && !this.previous.y 
             && this.updatePrevious(e);
    }

    handleGesture(e, {
      onLeft = () => { }, 
      onRight = () => { },
      onUp = () => { }, 
      onDown = () => { } }) 
  {
    let screenX =  e.changedTouches[0].screenX,
         screenY =  e.changedTouches[0].screenY;

    if (this.previous.x + this.offset < screenX) {
        log('right')
        this.updatePrevious(e)
        onRight()
        return
    }
    if (this.previous.x - this.offset > screenX) {
        log('left')
        this.updatePrevious(e)
        onLeft()
        return
    }
    if (this.previous.y + this.offset < screenY) {
        log('down')
        this.updatePrevious(e)
        onDown()
        return
    }
    if (this.previous.y - this.offset > screenY) {
        log('up')
        this.updatePrevious(e)
        onUp()
        return
    }
  }

  kill() { this.previous = { x: null, y: null } }
}

//edited "c7x43t's" code above...
//main method.
export default class Swipeable {
    constructor({ offset }) {
        this.root = new RootSwipeable({ offset })
        this.pageWidth = window.innerWidth || document.body.clientWidth
        this.threshold = Math.max(1, Math.floor(0.01 * (this.pageWidth)))
        this.touchstart = { x: 0, y: 0 }
        this.touchend = { x: 0, y: 0 }
        this.limit = Math.tan(45 * 1.5 / 180 * Math.PI)
    }

    touchStart(e) {
        root.init(e)
        this.touchstart.x = e.changedTouches[0].screenX
        this.touchstart.y = e.changedTouches[0].screenY
    }
    touchMove(e, {
        onLeft = () => { }, 
        onRight = () => { },
        onUp = () => { },
        onDown = () => { }
    }) {
        this.touchend.x = e.changedTouches[0].screenX;
        this.touchend.y = e.changedTouches[0].screenY;
        this.handleGesture(e, { onLeft, onRight, onUp, onDown })
    }

    handleGesture(e, { onLeft, onRight, onUp, onDown }) {
        let x = this.touchend.x - this.touchstart.x,
        y = this.touchend.y - this.touchstart.y;
    
        if (Math.abs(x) > this.threshold || Math.abs(y) > this.threshold) {
   
            root.handleGestures(e, {
               onUp: () => onUp(e, y),
               onRight: () => onRight(e, x),
               onDown: () => onDown(e, y),
               onLeft: () => onLeft(e, x)
            })

            //root.handleGestures() is the initial root class above.//
        }
    }

    touchEnd() {
        root.kill()
        this.touchstart = { x: 0, y: 0 }
        this.touchend = { x: 0, y: 0 }
    }
}

The module above will then be used as so

import Swipeable from '@file_location'

//offset is preferably a number >= 2 && <= 10.
const swipeable = new Swipeable({ offset: 2 })

//example component: carousel//
document.querySelector('.carousel').addEventListener('touchstart', e => swipeable.touchStart(e))
document.querySelector('.carousel').addEventListener('touchmove', e => {
    swipeable.touchMove(e, { onUp: (e, x) => console.log(e, x + 'px swiped') })
})
document.querySelector('.carousel').addEventListener('touchend', e => swipeable.touchEnd(e))

@bcanedo4
Copy link

Works great, thanks!

@hperrin
Copy link

hperrin commented Mar 28, 2021

@tryhardest

This is starting to look like one of the leading script threads on vanillaJS touch events. Seems like Hammer, Zing and Interactjs (pointer events) libs (interact excluded) are maybe become a bit obsolete except for drag, drop, and pointer support. Does anyone have any flushed out changes they have made since (as the thread goes back a few years)? Would be cool to keep this thread up to date with any changes or learnings. Anyone using this extensively with event listeners on touchMove? @evrenakar @tlacaelelrl @hperrin

Despite not doing any work on it in a couple years, I still do maintain and use my version with the additional features I mentioned:
https://github.com/sciactive/tinygesture

@god-s-perfect-idiot
Copy link

god-s-perfect-idiot commented May 23, 2021

For anyone still interesting, I just made a bit modified version to get swipe gesture controls on in-browser explorers.

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

    function handleGesture(touchstartX, touchstartY, touchendX, touchendY) {
        const delx = touchendX - touchstartX;
        const dely = touchendY - touchstartY;
        if(Math.abs(delx) > Math.abs(dely)){
            if(delx > 0) return "right"
            else return "left"
        }
        else if(Math.abs(delx) < Math.abs(dely)){
            if(dely > 0) return "down"
            else return "up"
        }
        else return "tap"
    }

 const gestureZone = document.getElementById('main');
    gestureZone.addEventListener('touchstart', function(event) {
        touchstartX = event.changedTouches[0].screenX;
        touchstartY = event.changedTouches[0].screenY;
    }, false);

    gestureZone.addEventListener('touchend', function(event) {
        touchendX = event.changedTouches[0].screenX;
        touchendY = event.changedTouches[0].screenY;
        alert(handleGesture(touchstartX, touchstartY, touchendX, touchendY))
    }, false); 

The source thread requires ultra precise swipes

@MondeLionel
Copy link

@c4benn

Ohh, yes. Your solution is a acting up on FF but it is the most elegant. Will do more testing.

@MiguelLaraDev
Copy link

Thank you, works fine for me!

@jonseo
Copy link

jonseo commented Jun 15, 2022

Quick dumb question, how do you write a function to count the number of swipes to the left or right?

@IanRr
Copy link

IanRr commented Jun 22, 2022

@jonseo I'd probably just create a variable or variables outside of the addEventListener scope (so basically just at the top of the code) and then increment those inside the addEventListener function

lmk if that's too brief a description

@jonseo
Copy link

jonseo commented Jun 23, 2022

Yeh, thats a bit brief, can u point me to some code examples. much appreciated.

@c4benni
Copy link

c4benni commented Jun 23, 2022

@jonseo check this https://gist.github.com/SleepWalker/da5636b1abcbaff48c4d?permalink_comment_id=3537435#gistcomment-3537435

especially

document.querySelector('.carousel').addEventListener('touchmove', e => {
    swipeable.touchMove(e, { 
        onUp: (e, x) => console.log(e, x + 'px swiped') ,
        onLeft: (e, x) => console.log(e, x + 'px swiped')
    })
})

I hope that's what you mean

@jonseo
Copy link

jonseo commented Jun 23, 2022

thnk you!

@wenlittleoil
Copy link

Great, but still have questions about different browser compatibility issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment