Détectez un glissement de doigt via JavaScript sur iPhone et Android

268

Comment pouvez-vous détecter qu'un utilisateur a glissé son doigt dans une certaine direction sur une page Web avec JavaScript?

Je me demandais s'il y avait une solution qui fonctionnerait pour les sites Web à la fois sur l'iPhone et sur un téléphone Android.

827
la source
1
Pour la reconnaissance par balayage, je recommanderais Hammer.js . Il est assez petit et prend en charge de nombreux gestes: - Balayez - Tournez - Pincez - Appuyez ( appui long) - Tapez - Pan
Will Brickner
Il y a un événement: "touchmove"
Clay
@Clay que l'on ne fonctionne toujours pas dans Safari donc pas d'iPhone.
Jakuje

Réponses:

342

Exemple de code JS vanilla simple:

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 ) {
            /* left swipe */ 
        } else {
            /* right swipe */
        }                       
    } else {
        if ( yDiff > 0 ) {
            /* up swipe */ 
        } else { 
            /* down swipe */
        }                                                                 
    }
    /* reset values */
    xDown = null;
    yDown = null;                                             
};

Testé sur Android.

givanse
la source
1
L' air cool et simple, toute idée de ce qui est le soutien à ces événements touchstart, touchmove?
d.raev
1
Cela fonctionne plutôt bien mais a un problème pour détecter les mouvements droits. Je posterai une autre réponse à ce sujet qui a corrigé cela en tant que solution JQuery (bureau). Il ajoute également la version souris de ces événements de balayage et ajoute une option de sensibilité.
Codebeat
1
Zut. Le sujet est fermé, je ne peux donc pas ajouter ma réponse!
Codebeat
3
Cela fonctionne très bien, mais gauche / droite et haut / bas sont à l'envers.
Peter Eisentraut
4
originalEvent est une propriété JQuery. Il doit être laissé de côté si vous exécutez du javascript pur sans JQuery. Le code actuel lève une exception s'il est exécuté sans JQuery.
Jan Derk
31

Sur la base de la réponse de @ givanse, voici comment vous pouvez le faire avec 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);
    }
}

Vous pouvez alors l'utiliser comme ceci:

// 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();
Marwelln
la source
7
ce code ne fonctionnera probablement pas parce que vous obtiendrez une exception en essayant d'appeler .bindundefined car vous handleTouchMoven'avez en fait rien renvoyé. il est également inutile d'appeler bind lors de l'appel de la fonction avec this.car il est déjà lié au contexte actuel
nick.skriabin
3
Je viens de retirer .bind(this);et cela a fonctionné gracieusement. merci @nicholas_r
Ali Ghanavatian
Récupérez l'élément vous-même Je supprime simplement '#' dans document.getElementById ('mon-élément') et cela a bien fonctionné. Merci @Marwelln :)
Blue Tram
4
Si vous voulez attendre jusqu'à ce que le balayage se termine (c'est-à-dire après avoir levé le doigt ou onmouseup), passez touches[0]à changedTouches[0]et le type de gestionnaire d'événements handleTouchMoveàhandleTouchEnd
TetraDev
appelez run()deux fois et vous obtenez une fuite de mémoire désagréable
Mat
20

J'ai fusionné quelques-unes des réponses ici dans un script qui utilise CustomEvent pour déclencher des événements balayés dans le DOM. Ajoutez le script 0.7k swiped-events.min.js à votre page et écoutez les événements glissés :

glissé vers la gauche

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

glissé vers la droite

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

balayé

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

glissé vers le bas

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

Vous pouvez également attacher directement à un élément:

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

Configuration en option

Vous pouvez spécifier les attributs suivants pour modifier le fonctionnement de l'interaction de balayage dans votre page (ils sont facultatifs) .

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

Le code source est disponible sur Github

John Doherty
la source
Je suis venu ici parce que pure-swipe ne fonctionnait pas pour moi sur MOBILE
StefanBob
@StefanBob si vous cochez la case github repo avec suffisamment d'informations pour me permettre de reproduire le problème, je vais l'examiner
John Doherty
1
Merci, ça marche parfaitement! J'ai remplacé Hammer.js par votre bibliothèque, car la première ne fonctionne pas avec le zoom du navigateur et c'est un sérieux problème d'utilisation. Avec cette bibliothèque, le zoom fonctionne correctement (testé sur Android)
collimarco
15

ce que j'ai utilisé auparavant, c'est que vous devez détecter l'événement mousedown, enregistrer son emplacement x, y (selon ce qui est pertinent), puis détecter l'événement mouseup et soustraire les deux valeurs.

bonjour
la source
28
Je crois que c'est touchstart, touchmove, touchcancel et touchend avec lesquels on pourrait travailler, pas mousedown ou mouseup.
Volomike
12

J'ai trouvé @givanse brillante réponse comme étant la plus fiable et compatible sur plusieurs navigateurs mobiles pour enregistrer des actions de balayage.

Cependant, il y a un changement dans son code nécessaire pour le faire fonctionner dans les navigateurs mobiles modernes qui utilisent jQuery.

event.touchesn'existera pas si jQueryest utilisé et résulte en undefinedet doit être remplacé par event.originalEvent.touches. Sans jQuery, event.touchesdevrait fonctionner correctement.

Donc, la solution devient,

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

Testé sur:

  • Android : Chrome, Navigateur UC
  • iOS : Safari, Chrome, Navigateur UC
nashcheez
la source
originalEvent est une propriété JQuery. Il n'existe même pas en Javascript pur.
Jan Derk
1
Conformément à cette réponse SO , un événement tactile s'il est pris en charge par le navigateur sera exposé event.originalEvent. La chose event.touchesa cessé d'exister maintenant et se traduit par undefined.
nashcheez
event.touches a cessé d'exister uniquement lors de l'utilisation de JQuery. Essayez votre code sans JQuery et vous obtiendrez une erreur indiquant que evt.originalEvent n'est pas défini. JQuery remplace totalement l'événement par le sien et met l'événement natif du navigateur dans originalevent. Version courte: votre code ne fonctionne qu'avec JQuery. Cela fonctionne sans JQuery si vous supprimez originalevent.
Jan Derk
1
Ouais, j'ai fait quelques recherches et j'ai réalisé que vous aviez raison sur la disponibilité de l'activation de jquery event.originalEvent. Je mettrai à jour ma réponse. Merci! :)
nashcheez
6

Un mod de réponse la plus élevée (ne peut pas commenter ...) pour faire face à de courts balayages

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;
    }
};
rmnsh
la source
6

trashold, 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_TRASHOLD = 200;
const  DIFF_TRASHOLD = 130;

function handleTouchEnd() {

let timeDiff = Date.now() - timeDown; 
if (Math.abs(xDiff) > Math.abs(yDiff)) { /*most significant*/
  if (Math.abs(xDiff) > DIFF_TRASHOLD && timeDiff < TIME_TRASHOLD) {
    if (xDiff > 0) {
      // console.log(xDiff, TIME_TRASHOLD, DIFF_TRASHOLD)
      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_TRASHOLD && timeDiff < TIME_TRASHOLD) {
    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;
}
Sergey Guns
la source
4

Si quelqu'un essaie d'utiliser jQuery Mobile sur Android et a des problèmes avec la détection de balayage JQM

(J'en avais sur Xperia Z1, Galaxy S3, Nexus 4 et certains téléphones Wiko aussi) cela peut être utile:

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

Le balayage sur Android n'a été détecté que s'il s'agissait d'un balayage très long, précis et rapide.

Avec ces deux lignes cela fonctionne correctement

Rayjax
la source
J'avais aussi besoin d'ajouter: $.event.special.swipe.scrollSupressionThreshold = 8;mais tu m'as mis dans la bonne direction! Je vous remercie!
Philip G
4

J'ai eu des problèmes avec le gestionnaire de touche qui tirait en continu pendant que l'utilisateur traînait un doigt. Je ne sais pas si cela est dû à quelque chose que je fais mal ou non, mais j'ai recâblé cela pour accumuler des mouvements avec touchmove et touchend déclenche le rappel.

J'avais également besoin d'avoir un grand nombre de ces instances et j'ai donc ajouté des méthodes d'activation / désactivation.

Et un seuil où un bref coup ne se déclenche pas. Touchstart zéro est les compteurs à chaque fois.

Vous pouvez modifier le target_node à la volée. L'activation à la création est facultative.

/** 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);
            }
        }
    }
}
Toews tendance
la source
Est-ce pour ES5 ou ES6?
1,21 gigawatts
@gigawatts Je ne me souviens pas. Le projet utilisé a déjà atteint EOL et je n'ai pas eu besoin du code depuis. Je soupçonne au moment où j'écrivais pour ES6 mais c'était il y a plus de 2 ans.
Trendal Toews
3

J'ai également fusionné quelques-unes des réponses, principalement la première et la seconde avec les classes, et voici ma 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;
    }
}

Ensuite, vous pouvez l'utiliser comme suit:

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

Il permet d'éviter les erreurs de console lorsque vous souhaitez appeler uniquement la méthode "onLeft".

Romanna Semenyshyn
la source
2

Si vous avez juste besoin de glisser, vous êtes mieux en termes de taille en utilisant uniquement la pièce dont vous avez besoin. Cela devrait fonctionner sur n'importe quel appareil tactile.

C'est ~ 450 octets après compression gzip, minification, babel etc.

J'ai écrit la classe ci-dessous sur la base des autres réponses, elle utilise un pourcentage déplacé au lieu de pixels et un modèle de répartiteur d'événements pour accrocher / décrocher les choses.

Utilisez-le comme ceci:

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;

Vargr
la source
2

Je voulais détecter le balayage gauche et droit uniquement, mais ne déclencher l'action que lorsque l'événement tactile se termine , j'ai donc légèrement modifié l'excellente réponse de @ givanse pour ce faire.

Pourquoi faire ça? Si, par exemple, en glissant, l'utilisateur remarque qu'il ne veut finalement pas glisser, il peut déplacer son doigt à la position d'origine (une application de téléphone "dating" très populaire le fait;)), puis le "glisser vers la droite" l'événement est annulé.

Donc, pour éviter un événement "glisser vers la droite" simplement parce qu'il y a une différence de 3 pixels horizontalement, j'ai ajouté un seuil en dessous duquel un événement est rejeté: pour avoir un événement "glisser vers la droite", l'utilisateur doit balayer au moins 1/3 de la largeur du navigateur (bien sûr, vous pouvez le modifier).

Tous ces petits détails améliorent l'expérience utilisateur. Voici le code (Vanilla JS):

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;
}
Basj
la source
1

Exemple simple de vanille JS pour un balayage horizontal:

let touchstartX = 0
let touchendX = 0

const slider = document.getElementById('slider')

function handleGesure() {
  if (touchendX < touchstartX) alert('swiped left!')
  if (touchendX > touchstartX) alert('swiped right!')
}

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

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

Vous pouvez utiliser la même logique pour le balayage vertical.

Damjan Pavlica
la source
1

Ajout à cette réponse ici . Celui-ci ajoute la prise en charge des événements de souris pour les tests sur le bureau:

<!--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,21 gigawatts
la source
1

J'ai retravaillé la solution de @givanse pour qu'elle fonctionne comme un hook React. L'entrée est des écouteurs d'événements facultatifs, la sortie est une référence fonctionnelle (doit être fonctionnelle pour que le hook puisse se réexécuter lorsque / si la référence change).

Également ajouté dans les paramètres de seuil de balayage vertical / horizontal, afin que de petits mouvements ne déclenchent pas accidentellement les écouteurs d'événements, mais ceux-ci peuvent être définis sur 0 pour imiter la réponse originale de plus près.

Conseil: pour de meilleures performances, les fonctions d'entrée de l'écouteur d'événements doivent être mémorisées.

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);
};
Ruben Martinez Jr.
la source
0

Un exemple d'utilisation avec 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]
}

Илья Зеленько
la source
J'utilise actuellement cette version. Comment pourrais-je empêcher cela de se déclencher plusieurs fois s'il est glissé à répétition? J'utilise cela avec la fonction d'animation pour une forme de défilement latéral et lorsque je glisse plusieurs fois, les choses deviennent un peu délirantes et mes divs commencent à se chevaucher dans la zone visible.
NMALM
0

Vous pourriez avoir plus de facilité à l'implémenter avec des événements de souris à prototyper.

Il existe de nombreuses réponses ici, y compris le haut, doivent être utilisées avec prudence car elles ne prennent pas en compte les cas de bord, en particulier autour des boîtes englobantes.

Voir:

Vous devrez expérimenter pour attraper les cas de bord et les comportements tels que le pointeur se déplaçant à l'extérieur de l'élément avant de se terminer.

Un balayage est un geste très basique qui est un niveau supérieur de traitement d'interaction de pointeur d'interface à peu près entre le traitement des événements bruts et la reconnaissance de l'écriture manuscrite.

Il n'y a pas de méthode exacte unique pour détecter un coup ou une aventure, bien que pratiquement tous suivent généralement un principe de base de détection d'un mouvement à travers un élément avec un seuil de distance et de vitesse ou de vitesse. Vous pourriez simplement dire que s'il y a un mouvement sur 65% de la taille de l'écran dans une direction donnée dans un délai donné, c'est un coup. C'est à vous de décider exactement où vous tracez la ligne et comment vous la calculez.

Certains pourraient également le regarder du point de vue de l'élan dans une direction et de la distance à laquelle il a été poussé lorsque l'élément est relâché. Cela est plus clair avec des glissements collants où l'élément peut être déplacé, puis au relâchement, il rebondira ou volera sur l'écran comme si l'élastique se cassait.

Il est probablement idéal d'essayer de trouver une bibliothèque de gestes que vous pouvez porter ou réutiliser, qui est couramment utilisée pour la cohérence. Beaucoup d'exemples ici sont excessivement simplistes, enregistrant un coup comme le moindre contact dans n'importe quelle direction.

Android serait le choix évident mais a le problème opposé, il est trop complexe.

Beaucoup de gens semblent avoir mal interprété la question comme tout mouvement dans une direction. Un balayage est un mouvement large et relativement bref dans une grande majorité de directions (bien qu'il puisse être arqué et avoir certaines propriétés d'accélération). Une aventure est similaire, mais a l'intention de propulser négligemment un élément à une bonne distance sous son propre élan.

Les deux sont suffisamment similaires pour que certaines bibliothèques ne fournissent que fling ou swipe, qui peuvent être utilisées de manière interchangeable. Sur un écran plat, il est difficile de vraiment séparer les deux gestes et, d'une manière générale, les gens font les deux (en faisant glisser l'écran physique mais en jetant l'élément d'interface utilisateur affiché à l'écran).

Votre meilleure option est de ne pas le faire vous-même. Il existe déjà un grand nombre de bibliothèques JavaScript pour détecter les gestes simples .

jgmjgm
la source