Wykryj przeciągnięcie palcem przez JavaScript na telefonie iPhone i Androidzie

268

Jak wykryć, że użytkownik przesunął palcem w jakimś kierunku po stronie internetowej z JavaScript?

Zastanawiałem się, czy istnieje jedno rozwiązanie, które działałoby na stronach internetowych zarówno na iPhonie, jak i telefonie z Androidem.

827
źródło
1
Do rozpoznawania machnięcia polecam Hammer.js . Jest dość mały i obsługuje wiele gestów: - Przesuń - Obróć - Uszczypnij - Naciśnij (przytrzymaj) - Stuknij - Przesuń
Will Brickner
Jest wydarzenie: „touchmove”
Clay
@Zagraj, że nadal nie działa w Safari, więc nie ma iPhone'a.
Jakuje

Odpowiedzi:

341

Prosty przykładowy waniliowy kod JS:

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

Testowane w systemie Android.

ofiarować
źródło
1
Wygląda świetnie i proste, każdy pomysł co jest poparcie dla tego wydarzenia touchstart, touchmove?
d.raev
1
Działa całkiem dobrze, ale ma problem z wykrywaniem prostych ruchów. Napiszę kolejną odpowiedź na ten temat, która naprawiła to jako rozwiązanie JQuery (na komputery). Dodaje również wersję tych zdarzeń przeciągania myszą i dodaje opcję czułości.
Codebeat
1
Cholera. Temat jest zamknięty, więc nie mogę dodać mojej odpowiedzi!
Codebeat
3
Działa to świetnie, ale lewo / prawo i góra / dół są do tyłu.
Peter Eisentraut
4
originalEvent jest właściwością JQuery. Należy go pominąć, jeśli używasz czystego javascript bez JQuery. Obecny kod zgłasza wyjątek, jeśli jest uruchamiany bez JQuery.
Jan Derk
31

Na podstawie odpowiedzi @ givanse możesz to zrobić za pomocą 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);
    }
}

Możesz go użyć w następujący sposób:

// 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
źródło
7
ten kod prawdopodobnie nie zadziała, ponieważ otrzymasz wyjątek podczas próby wywołania .bindniezdefiniowanego, ponieważ w handleTouchMoverzeczywistości nic nie zwróciłeś. również bezużyteczne jest wywoływanie wiązania podczas wywoływania funkcji, this.ponieważ jest już związane z bieżącym kontekstem
nick.skriabin
3
Właśnie usunąłem .bind(this);i zadziałało z wdziękiem. dziękuję @nicholas_r
Ali
Część sam pobierz element, po prostu usuwam „#” w document.getElementById („my-element”) i działało dobrze. Dziękuję @Marwelln :)
Blue Tram
4
Jeśli chcesz poczekać, aż przesunie się ENDS (czyli po tym, jak podniosą palec lub uniesienie myszy), zmień touches[0]na changedTouches[0]i typ obsługi zdarzenia handleTouchMovenahandleTouchEnd
TetraDev 19.04.19
zadzwoń run()dwa razy, a pojawi się nieprzyjemny wyciek pamięci
Mat
20

Połączyłem kilka odpowiedzi tutaj w skrypcie, który używa CustomEvent do uruchamiania zdarzeń przeciągnięcia w DOM. Dodaj 0.7k przeciągnął-events.min.js skrypt do swojej strony i nasłuchiwać przeciągnął zdarzeń:

przesunął w lewo

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

przesunął w prawo

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

zamieciony

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

przesunięty w dół

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

Możesz także dołączyć bezpośrednio do elementu:

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

Opcjonalna konfiguracja

Możesz określić następujące atrybuty, aby dostosować funkcjonowanie interakcji przeciągania na stronie (są one opcjonalne) .

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

Kod źródłowy jest dostępny na Github

John Doherty
źródło
Przybyłem tutaj, ponieważ czysta machnięcie nie działało dla mnie na MOBILE
StefanBob
@StefanBob, jeśli podniesiesz zaznaczenie na repozytorium github z wystarczającą ilością informacji, aby umożliwić mi odtworzenie problemu, przyjrzę się temu
John Doherty
1
Dzięki, działa idealnie! Zamieniłem Hammer.js na twoją bibliotekę, ponieważ ta pierwsza nie działa z powiększeniem przeglądarki i jest to poważny problem z użytecznością. Dzięki tej bibliotece zoom działa poprawnie (testowany na Androidzie)
collimarco,
15

to, czego użyłem wcześniej, to wykrycie zdarzenia mousedown, zarejestrowanie jego lokalizacji x, y (w zależności od tego, co jest istotne), a następnie wykrycie zdarzenia mysi i odjęcie dwóch wartości.

cześć
źródło
28
Wierzę, że to touchstart, touchmove, touchcancel i touchchend, z którymi można pracować, a nie mousedown lub mouseup.
Volomike,
12

Uważam, że świetna odpowiedź @givanse jest najbardziej niezawodna i kompatybilna z wieloma przeglądarkami mobilnymi do rejestrowania operacji przeciągania.

Jednak jego kod wymaga zmiany, aby działał we współczesnych przeglądarkach mobilnych, które używają jQuery.

event.touchesnie będzie istniał, jeśli jQueryzostanie użyty, a wyniki undefinedzostaną zastąpione przez event.originalEvent.touches. Bez jQuery, event.touchespowinny działać prawidłowo.

Rozwiązaniem staje się

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

Testowane na:

  • Android : Chrome, przeglądarka UC
  • iOS : Safari, Chrome, UC Browser
Nashcheez
źródło
originalEvent jest właściwością JQuery. Nie istnieje nawet w czystym JavaScript.
Jan Derk
1
Zgodnie z tą odpowiedzią SO zdarzenie dotykowe, jeśli jest obsługiwane przez przeglądarkę, zostanie ujawnione event.originalEvent. Rzecz event.touchesprzestała istnieć teraz i skutkuje undefined.
nashcheez
event.touches przestało istnieć tylko podczas używania JQuery. Wypróbuj swój kod bez JQuery, a otrzymasz błąd, że evt.originalEvent jest niezdefiniowany. JQuery całkowicie zastępuje zdarzenie swoim własnym i umieszcza natywne zdarzenie przeglądarki w originalevent. Krótka wersja: Twój kod działa tylko z JQuery. Działa bez JQuery, jeśli usuniesz originalevent.
Jan Derk
1
Tak, trochę przestudiowałem i zdałem sobie sprawę, że masz rację co do dostępności włączania jquery event.originalEvent. Zaktualizuję swoją odpowiedź. Dzięki! :)
nashcheez
6

Jakiś mod najwyższej odpowiedzi (nie może komentować ...), aby poradzić sobie z krótkimi przeciągnięciami

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
źródło
6

kosz, przeciągnięcie limitu czasu, dodawanie swipeBlockElems.

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
źródło
4

Jeśli ktoś próbuje używać jQuery Mobile na Androidzie i ma problemy z wykrywaniem przesuwania JQM

(Miałem trochę na Xperia Z1, Galaxy S3, Nexus 4 i niektóre telefony Wiko). Może to być przydatne:

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

Przesunięcie na Androidzie nie zostało wykryte, chyba że było to bardzo długie, precyzyjne i szybkie przeciągnięcie.

Z tymi dwiema liniami działa poprawnie

Rayjax
źródło
Musiałem także dodać: $.event.special.swipe.scrollSupressionThreshold = 8;ale skierowałeś mnie w dobrym kierunku! Dziękuję Ci!
Philip G,
4

Miałem problem z ciągłym ostrzeżeniem, gdy użytkownik przeciągał palcem po okolicy. Nie wiem, czy to z powodu czegoś, co robię źle, czy nie, ale ponownie to przełączyłem, aby gromadzić ruchy za pomocą touchmove, a touchend faktycznie wyzwala oddzwonienie.

Musiałem także mieć dużą liczbę tych instancji, dlatego dodałem metody włączania / wyłączania.

I próg, w którym krótkie machnięcie nie strzela. Touchstart zero za każdym razem liczniki.

Możesz zmienić węzeł target_node w locie. Włącz przy tworzeniu jest opcjonalny.

/** 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);
            }
        }
    }
}
Trendy Toews
źródło
Czy to dotyczy ES5 czy ES6?
1,21 gigawata
@gawawatts Nie pamiętam. Projekt, który wykorzystał, osiągnął już EOL i od tego czasu nie potrzebowałem kodu. Podejrzewam, że w tym czasie pisałem dla ES6, ale to było ponad 2 lata temu.
Trendal Toews
3

Połączyłem też kilka odpowiedzi, głównie pierwszą i drugą z klasami, a oto moja wersja:

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

Następnie można użyć go w następujący sposób:

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

Pomaga uniknąć błędów konsoli, gdy chcesz wywoływać tylko powiedzmy metodę „onLeft”.

Romanna Semenyshyn
źródło
2

Jeśli potrzebujesz tylko machnięcia, lepiej jest użyć mniejszego rozmiaru, używając tylko potrzebnej części. Powinno to działać na dowolnym urządzeniu dotykowym.

Jest to ~ 450 bajtów po kompresji gzip, minimalizacji, babel itp.

Napisałem niższą klasę w oparciu o inne odpowiedzi, używa ona przesunięcia procentowego zamiast pikseli oraz wzorca dyspozytora zdarzeń do zaczepiania / odczepiania rzeczy.

Używaj go w ten sposób:

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
źródło
2

Chciałem, aby wykryć tylko lewy i prawy przesunąć, ale wywołać działania tylko wtedy, gdy zdarzenie dotykowy końce , więc lekko zmodyfikowany wielką odpowiedź znaku @ givanse aby to zrobić.

Po co to robić? Jeśli na przykład podczas przeciągania użytkownik zauważy, że w końcu nie chce przesunąć, może przesunąć palec w pierwotnej pozycji (robi to bardzo popularna aplikacja do randek;)), a następnie „przesuń w prawo” wydarzenie zostało anulowane.

Aby więc uniknąć zdarzenia „przeciągnij w prawo” tylko dlatego, że istnieje różnica 3 pikseli w poziomie, dodałem próg, poniżej którego zdarzenie jest odrzucane: aby mieć zdarzenie „przeciągnięcie w prawo”, użytkownik musi przesunąć przynajmniej 1/3 szerokości przeglądarki (oczywiście możesz to zmienić).

Wszystkie te małe szczegóły zwiększają komfort użytkowania. Oto kod (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
źródło
1

Prosty waniliowy przykład JS dla przesunięcia poziomego:

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()
})

Możesz użyć tej samej logiki do pionowego przeciągnięcia.

Damjan Pavlica
źródło
1

Dodanie do tej odpowiedzi tutaj . Ten dodaje obsługę zdarzeń myszy do testowania na komputerze:

<!--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 gigawata
źródło
0

Przykład użycia z przesunięciem.

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

Илья Зеленько
źródło
Obecnie korzystam z tej wersji. Jak mogę zapobiec wielokrotnemu wystrzeliwaniu, jeśli zostanie przeciągnięty w powtórce? Używam tego z funkcją animacji dla formy przewijania w bok, a kiedy kilkakrotnie przesuwam, rzeczy stają się trochę krzywe, a moje div zaczynają się nakładać w widocznym obszarze.
NMALM
0

Być może łatwiej będzie ci najpierw zaimplementować go za pomocą zdarzeń myszy do prototypowania.

Jest tu wiele odpowiedzi, w tym górna, należy zachować ostrożność, ponieważ nie uwzględniają przypadków krawędzi, szczególnie wokół obwiedni.

Widzieć:

Będziesz musiał eksperymentować, aby uchwycić przypadki krawędzi i zachowania, takie jak wskaźnik poruszający się poza elementem przed zakończeniem.

Przesunięcie jest bardzo podstawowym gestem, który stanowi wyższy poziom przetwarzania interakcji interfejsu wskaźnikowego z grubsza siedzącego między przetwarzaniem nieprzetworzonych zdarzeń a rozpoznawaniem pisma ręcznego.

Nie ma jednej dokładnej metody wykrywania przesunięcia lub rzutu, chociaż praktycznie wszystkie zasadniczo przestrzegają podstawowej zasady wykrywania ruchu w poprzek elementu o progu odległości i prędkości lub prędkości. Można po prostu powiedzieć, że jeśli ruch w 65% rozmiaru ekranu w danym kierunku następuje w danym czasie, jest to przesunięcie. To, gdzie narysujesz linię i jak ją obliczysz, zależy od ciebie.

Niektórzy mogą również patrzeć na to z perspektywy pędu w kierunku i jak daleko od ekranu został wypchnięty, gdy element zostanie zwolniony. Jest to wyraźniejsze dzięki lepkim przeciągnięciom, w których element można przeciągnąć, a następnie podczas zwalniania albo odbije się do tyłu, albo odleci z ekranu, jakby sprężysta pękła.

Prawdopodobnie idealnie jest znaleźć bibliotekę gestów, którą można przenieść lub użyć ponownie, która jest zwykle używana w celu zachowania spójności. Wiele przykładów tutaj jest nadmiernie uproszczonych, rejestrując machnięcie jako najmniejszy dotyk w dowolnym kierunku.

Android byłby oczywistym wyborem, choć ma odwrotny problem, jest zbyt skomplikowany.

Wydaje się, że wiele osób błędnie zinterpretowało to pytanie jako każdy ruch w danym kierunku. Przeciągnięcie to szeroki i stosunkowo krótki ruch w przeważającej części w jednym kierunku (choć może być łukowy i mieć pewne właściwości przyspieszające). Rzut jest podobny, choć ma zamiar niedbale wypchnąć przedmiot na znaczną odległość pod własnym pędem.

Te dwie są wystarczająco podobne, że niektóre biblioteki mogą zapewniać tylko fling lub machnięcie, których można używać zamiennie. Na płaskim ekranie trudno jest naprawdę rozdzielić dwa gesty i ogólnie mówiąc, ludzie robią oba (przesuwając ekran fizyczny, ale odrzucając element interfejsu użytkownika wyświetlany na ekranie).

Najlepszą opcją jest nie robić tego sam. Istnieje już duża liczba bibliotek JavaScript do wykrywania prostych gestów .

jgmjgm
źródło
0

I przerobione @givanse rozwiązanie jest do działania jako React haka. Dane wejściowe to niektóre opcjonalne detektory zdarzeń, dane wyjściowe to funkcjonalny ref (musi być funkcjonalny, aby można było ponownie uruchomić hak, gdy / jeśli ref zmieni).

Dodano również parametr progu przesunięcia w pionie / poziomie, aby małe ruchy nie wyzwalały przypadkowo detektorów zdarzeń, ale można je ustawić na 0, aby dokładniej naśladować oryginalną odpowiedź.

Wskazówka: aby uzyskać najlepszą wydajność, funkcje wejściowe detektora zdarzeń powinny zostać zapamiętane.

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.
źródło