Mendeteksi usapan jari melalui JavaScript di iPhone dan Android

268

Bagaimana Anda bisa mendeteksi bahwa pengguna menggesekkan jarinya ke suatu arah di atas halaman web dengan JavaScript?

Saya bertanya-tanya apakah ada satu solusi yang akan berfungsi untuk situs web di iPhone dan ponsel Android.

827
sumber
1
Untuk pengenalan gesek, saya akan merekomendasikan Hammer.js . Ini cukup kecil, dan mendukung banyak gerakan: - Geser - Putar - Jepit - Tekan (tahan lama) - Ketuk - Pan
Will Brickner
Ada sebuah acara: "touchmove"
Clay
@ Mainkan yang satu masih tidak berfungsi di Safari jadi tidak ada iPhone.
Jakuje

Jawaban:

342

Contoh kode vanilla JS sederhana:

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

Diuji di Android.

Givanse
sumber
1
Looks dingin dan sederhana, tahu apa adalah dukungan untuk acara ini touchstart, touchmove?
d.raev
1
Ini bekerja cukup baik tetapi memiliki masalah dalam mendeteksi gerakan lurus. Saya akan memposting jawaban lain pada topik ini yang memperbaiki ini sebagai solusi JQuery (desktop). Itu juga menambahkan versi mouse dari peristiwa-peristiwa swipe ini dan menambahkan opsi sensitivitas.
Codebeat
1
Sial. Topik ditutup sehingga tidak dapat menambahkan jawaban saya!
Codebeat
3
Ini bekerja dengan baik, tetapi kiri / kanan dan atas / bawah adalah mundur.
Peter Eisentraut
4
originalEvent adalah properti JQuery. Itu harus ditinggalkan jika Anda menjalankan javascript murni tanpa JQuery. Kode saat ini memunculkan pengecualian jika dijalankan tanpa JQuery.
Jan Derk
31

Berdasarkan jawaban @ givanse, ini adalah bagaimana Anda dapat melakukannya dengan 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);
    }
}

Anda dapat menggunakannya seperti ini:

// 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
sumber
7
kode ini mungkin tidak akan berfungsi karena Anda akan mendapatkan pengecualian saat mencoba memanggil .bindundefined karena Anda handleTouchMovesebenarnya tidak mengembalikan apa pun. juga tidak ada gunanya untuk memanggil bind ketika memanggil fungsi dengan this.karena sudah terikat dengan konteks saat ini
nick.skriabin
3
Saya baru saja dihapus .bind(this);dan itu bekerja dengan anggun. terima kasih @nicholas_r
Ali Ghanavatian
Bagian mendapatkan elemen sendiri, saya hanya menghapus '#' di document.getElementById ('my-element') dan itu bekerja dengan baik. Terima kasih @Marwelln :)
Trem Biru
4
Jika Anda ingin menunggu hingga swipe ENDS (artinya setelah mereka mengangkat jari atau onmouseup), ubah touches[0]ke changedTouches[0]dan tipe event handler handleTouchMovekehandleTouchEnd
TetraDev
telepon run()dua kali dan Anda mendapatkan kebocoran memori yang tidak menyenangkan
Mat
20

Saya menggabungkan beberapa jawaban di sini ke dalam skrip yang menggunakan CustomEvent untuk memecat acara yang digesek di DOM. Tambahkan skrip 0.7k swiped-events.min.js ke halaman Anda dan dengarkan acara swiped :

digesekkan ke kiri

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

digesekkan ke kanan

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

digesek

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

digesek ke bawah

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

Anda juga dapat melampirkan langsung ke elemen:

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

Konfigurasi opsional

Anda dapat menentukan atribut berikut untuk menyesuaikan bagaimana fungsi interaksi swipe di halaman Anda (ini adalah opsional) .

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

Kode sumber tersedia di Github

John Doherty
sumber
Saya datang ke sini karena swipe murni tidak berfungsi untuk saya di MOBILE
StefanBob
@StefanBob jika Anda menaikkan centang pada repo github dengan informasi yang cukup untuk memungkinkan saya mereproduksi masalah ini, saya akan memeriksanya
John Doherty
1
Terima kasih, ini bekerja dengan sempurna! Saya mengganti Hammer.js dengan perpustakaan Anda, karena yang pertama tidak bekerja dengan zoom browser dan itu adalah masalah kegunaan yang serius. Dengan perpustakaan ini zoom berfungsi dengan baik (diuji pada Android)
collimarco
15

apa yang saya gunakan sebelumnya adalah Anda harus mendeteksi acara mousedown, merekam lokasi x, y (mana yang relevan) kemudian mendeteksi acara mouseup, dan kurangi dua nilai.

helloandre
sumber
28
Saya percaya itu touchstart, touchmove, touchcancel, dan touchend yang akan bekerja dengan seseorang, bukan mouse atau mouseup.
Volomike
12

Saya telah menemukan jawaban brilian @givanse sebagai yang paling dapat diandalkan dan kompatibel di beberapa browser seluler untuk mendaftarkan tindakan gesek.

Namun, ada perubahan dalam kodenya yang diperlukan untuk membuatnya berfungsi di browser seluler modern yang menggunakan jQuery.

event.touchestidak akan ada jika jQuerydigunakan dan menghasilkan undefineddan harus diganti oleh event.originalEvent.touches. Tanpa jQuery, event.touchesseharusnya bekerja dengan baik.

Jadi solusinya menjadi,

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

Diuji pada:

  • Android : Chrome, UC Browser
  • iOS : Safari, Chrome, UC Browser
nashcheez
sumber
originalEvent adalah properti JQuery. Bahkan tidak ada di Javascript murni.
Jan Derk
1
Sesuai jawaban SO ini , acara sentuh jika didukung oleh browser akan diekspos melalui event.originalEvent. Masalahnya event.touchessudah tidak ada lagi sekarang dan menghasilkan undefined.
nashcheez
event.touches hanya berhenti ada saat menggunakan JQuery. Coba kode Anda tanpa JQuery dan Anda akan mendapatkan kesalahan bahwa evt.originalEvent tidak terdefinisi. JQuery sepenuhnya menggantikan acara dengan miliknya dan menempatkan acara browser asli di originalevent. Versi singkat: Kode Anda hanya berfungsi dengan JQuery. Ini bekerja tanpa JQuery jika Anda menghapus originalevent.
Jan Derk
1
Ya saya sedikit meneliti dan menyadari Anda benar tentang ketersediaan mengaktifkan jquery event.originalEvent. Saya akan memperbarui jawaban saya. Terima kasih! :)
nashcheez
6

Beberapa mod jawaban uppest (tidak dapat mengomentari ...) untuk menangani gesekan singkat

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
sumber
6

trashold, sapuan timeout, tambahkan 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
sumber
4

Jika ada yang mencoba menggunakan jQuery Mobile di Android dan memiliki masalah dengan deteksi gesek JQM

(Saya punya beberapa di Xperia Z1, Galaxy S3, Nexus 4 dan beberapa telepon Wiko juga) ini bisa berguna:

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

Gesek pada android tidak terdeteksi kecuali itu gesekan yang sangat panjang, tepat dan cepat.

Dengan dua garis ini berfungsi dengan benar

Rayjax
sumber
Saya juga perlu menambahkan: $.event.special.swipe.scrollSupressionThreshold = 8;tetapi Anda menempatkan saya di arah yang benar! Terima kasih!
Philip G
4

Saya mengalami masalah dengan handler touchend menembak terus menerus sementara pengguna menyeret jari. Saya tidak tahu apakah itu karena sesuatu yang saya lakukan salah atau tidak, tetapi saya mengembalikan ini untuk mengumpulkan gerakan dengan touchmove dan touchend benar-benar memicu callback.

Saya juga perlu memiliki banyak contoh ini dan saya menambahkan metode aktifkan / nonaktifkan.

Dan ambang batas di mana gesekan singkat tidak menyala. Touchstart nol adalah penghitung setiap kali.

Anda dapat mengubah target_node dengan cepat. Mengaktifkan penciptaan adalah opsional.

/** 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);
            }
        }
    }
}
Toew Trendal
sumber
Apakah ini untuk ES5 atau ES6?
1,21 gigawatt
@gigawatts saya tidak ingat. Proyek yang digunakan sudah mencapai EOL dan saya tidak memerlukan kode sejak itu. Saya menduga pada saat saya menulis untuk ES6 tapi itu lebih dari 2 tahun yang lalu.
Trendal Toews
3

Saya telah menggabungkan beberapa jawaban juga, sebagian besar yang pertama dan yang kedua dengan kelas, dan inilah versi saya:

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

Setelah itu dapat menggunakannya sebagai berikut:

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

Ini membantu untuk menghindari kesalahan konsol ketika Anda ingin memanggil hanya katakanlah metode "onLeft".

Romanna Semenyshyn
sumber
2

Jika Anda hanya perlu menggesek, Anda lebih baik dari ukuran bijaksana hanya menggunakan bagian yang Anda butuhkan. Ini harus bekerja pada perangkat sentuh apa pun.

Ini ~ 450 byte setelah kompresi gzip, minifikasi, babel dll.

Saya menulis kelas di bawah ini berdasarkan jawaban yang lain, menggunakan persentase yang dipindahkan alih-alih piksel, dan pola dispatcher acara untuk mengaitkan / melepas kaitan sesuatu.

Gunakan seperti ini:

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
sumber
2

Saya ingin mendeteksi geser ke kiri dan kanan saja, tetapi memicu tindakan hanya ketika acara sentuh berakhir , jadi saya sedikit mengubah jawaban hebat @ givanse untuk melakukan itu.

Kenapa melakukan itu? Jika misalnya, saat menggesekkan, pengguna melihat ia akhirnya tidak ingin menggesek, ia dapat menggerakkan jarinya di posisi semula. (aplikasi telepon "kencan" yang sangat populer melakukan ini;)), dan kemudian "geser ke kanan" acara dibatalkan.

Jadi untuk menghindari peristiwa "gesek ke kanan" hanya karena ada perbedaan 3px secara horizontal, saya menambahkan ambang di mana peristiwa dibuang: untuk memiliki peristiwa "gesek ke kanan", pengguna harus menggesek setidaknya 1/3 dari lebar browser (tentu saja Anda dapat memodifikasi ini).

Semua detail kecil ini meningkatkan pengalaman pengguna. Berikut adalah kode (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
sumber
1

Contoh vanilla JS sederhana untuk sapuan 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()
})

Anda dapat menggunakan logika yang hampir sama untuk sapuan vertikal.

Damjan Pavlica
sumber
1

Menambah jawaban ini di sini . Yang ini menambahkan dukungan untuk acara mouse untuk pengujian di desktop:

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

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

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

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

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

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

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

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

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

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

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

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

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

Saya mengerjakan ulang solusi @givanse agar berfungsi sebagai hook React. Input adalah beberapa pendengar acara opsional, output adalah ref fungsional (perlu fungsional sehingga kait dapat dijalankan kembali ketika / jika ref berubah).

Juga ditambahkan di param ambang geseran vertikal / horizontal, sehingga gerakan kecil tidak secara tidak sengaja memicu acara pendengar, tetapi ini dapat diatur ke 0 untuk meniru jawaban asli lebih dekat.

Kiat: untuk kinerja terbaik, fungsi input pendengar acara harus ditulis memo.

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.
sumber
0

Contoh cara menggunakan dengan 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]
}

Илья Зеленько
sumber
Saat ini menggunakan versi ini. Bagaimana saya mencegah ini dari menembak berkali-kali jika digesek dalam pengulangan? Saya menggunakan ini dengan fitur animate untuk bentuk sidescrolling dan ketika saya menggesek beberapa kali, semuanya menjadi sedikit kacau dan div saya mulai tumpang tindih di area yang terlihat.
NMALM
0

Anda mungkin memiliki waktu yang lebih mudah untuk menerapkannya terlebih dahulu dengan acara mouse ke prototipe.

Ada banyak jawaban di sini, termasuk bagian atas, harus digunakan dengan hati-hati karena mereka tidak mempertimbangkan kasus tepi terutama di sekitar kotak pembatas.

Lihat:

Anda perlu bereksperimen untuk menangkap kasus dan perilaku tepi seperti penunjuk yang bergerak di luar elemen sebelum berakhir.

Babatan adalah gerakan yang sangat dasar yang merupakan tingkat yang lebih tinggi dari pemrosesan interaksi penunjuk antarmuka kira-kira duduk antara memproses peristiwa mentah dan pengenalan tulisan tangan.

Tidak ada metode tunggal yang tepat untuk mendeteksi gesekan atau gesekan meskipun hampir semua umumnya mengikuti prinsip dasar mendeteksi gerakan melintasi elemen dengan ambang jarak dan kecepatan atau kecepatan. Anda mungkin hanya mengatakan bahwa jika ada gerakan melintasi 65% dari ukuran layar dalam arah tertentu dalam waktu tertentu maka itu adalah swipe. Tepat di mana Anda menggambar garis dan bagaimana Anda menghitungnya, itu terserah Anda.

Beberapa orang mungkin juga melihatnya dari perspektif momentum ke arah dan seberapa jauh dari layar telah didorong ketika elemen dilepaskan. Ini lebih jelas dengan gesekan lengket di mana elemen dapat diseret dan kemudian pada rilis akan bangkit kembali atau terbang dari layar seolah-olah elastis pecah.

Mungkin ideal untuk mencoba menemukan perpustakaan gerakan yang dapat Anda porting atau gunakan kembali yang biasa digunakan untuk konsistensi. Banyak contoh di sini terlalu sederhana, mendaftarkan gesekan sebagai sentuhan sedikit ke segala arah.

Android akan menjadi pilihan yang jelas meskipun memiliki masalah yang berlawanan, itu terlalu rumit.

Banyak orang tampaknya salah mengartikan pertanyaan itu sebagai gerakan ke arah mana pun. Babatan adalah gerakan yang luas dan relatif singkat yang sangat dalam satu arah (meskipun dapat melengkung dan memiliki sifat percepatan tertentu). Serupa adalah serupa meskipun bermaksud untuk dengan santai mendorong item jauh jarak yang adil di bawah momentumnya sendiri.

Keduanya cukup mirip bahwa beberapa perpustakaan hanya menyediakan fling atau swipe, yang dapat digunakan secara bergantian. Pada layar datar, sulit untuk benar-benar memisahkan kedua gerakan dan secara umum orang melakukan keduanya (menggesekkan layar fisik tetapi melemparkan elemen UI yang ditampilkan di layar).

Pilihan terbaik Anda adalah tidak melakukannya sendiri. Sudah ada banyak perpustakaan JavaScript untuk mendeteksi gerakan sederhana .

jgmjgm
sumber