Phát hiện ngón tay vuốt qua JavaScript trên iPhone và Android


268

Làm thế nào bạn có thể phát hiện ra rằng một người dùng đã vuốt ngón tay của mình theo một hướng nào đó trên một trang web bằng JavaScript?

Tôi đã tự hỏi nếu có một giải pháp sẽ hoạt động cho các trang web trên cả iPhone và điện thoại Android.


1
Để nhận dạng vuốt, tôi muốn giới thiệu Hammer.js . Nó khá nhỏ và hỗ trợ nhiều cử chỉ: - Vuốt - Xoay - Chụm - Bấm (giữ lâu) - Chạm - Pan
Will Brickner

Có một sự kiện: "touchmove"
Clay

@Clay mà một người vẫn không hoạt động trong Safari nên không có iPhone.
Jakuje

Câu trả lời:


341

Mẫu mã vanilla đơn giản:

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

Đã thử nghiệm trên Android.


1
Vẻ mát mẻ và đơn giản, bất kỳ ý tưởng gì là sự hỗ trợ cho các sự kiện này touchstart, touchmove?
d.raev

1
Nó hoạt động khá tốt nhưng có vấn đề phát hiện các chuyển động thẳng. Tôi sẽ đăng một câu trả lời khác tại chủ đề này đã sửa nó thành giải pháp JQuery (máy tính để bàn). Nó cũng thêm phiên bản chuột của các sự kiện vuốt này và thêm tùy chọn độ nhạy.
Codebeat

1
Chỉ trích. Chủ đề đã bị đóng nên không thể thêm câu trả lời của tôi!
Codebeat

3
Điều này hoạt động rất tốt, nhưng trái / phải và lên / xuống là ngược.
Peter Eisentraut

4
originalEvent là một thuộc tính JQuery. Nó nên bị bỏ qua nếu bạn chạy javascript thuần mà không có JQuery. Mã hiện tại tăng một ngoại lệ nếu chạy mà không có JQuery.
Jan Derk

31

Dựa trên câu trả lời của @ givanse, đây là cách bạn có thể làm với 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);
    }
}

Bạn có thể sử dụng nó như thế này:

// Use class to get element by string.
var swiper = new Swipe('#my-element');
swiper.onLeft(function() { alert('You swiped left.') });
swiper.run();

// Get the element yourself.
var swiper = new Swipe(document.getElementById('#my-element'));
swiper.onLeft(function() { alert('You swiped left.') });
swiper.run();

// One-liner.
(new Swipe('#my-element')).onLeft(function() { alert('You swiped left.') }).run();

7
mã này có thể không hoạt động vì bạn sẽ gặp ngoại lệ trong khi cố gắng gọi .bindkhông xác định vì handleTouchMovethực tế bạn không trả lại bất cứ điều gì. Ngoài ra, việc gọi liên kết khi gọi hàm là vô ích this.vì nó đã bị ràng buộc với bối cảnh hiện tại
nick.skriabin

3
Tôi vừa gỡ .bind(this); và nó hoạt động duyên dáng. cảm ơn bạn @nicholas_r
Ali Ghanavatian

Một phần tự lấy phần tử Tôi chỉ cần xóa '#' trong document.getEuityById ('phần tử của tôi') và nó hoạt động tốt. Cảm ơn bạn @Marwelln :)
Blue Trâm

4
Nếu bạn muốn đợi cho đến khi vuốt ENDS (nghĩa là sau khi họ nhấc ngón tay hoặc onmouseup), hãy đổi touches[0]thành changedTouches[0]và loại xử lý sự kiện handleTouchMovethànhhandleTouchEnd
TetraDev

gọi run()hai lần và bạn bị rò rỉ bộ nhớ khó chịu
Mat

20

Tôi đã hợp nhất một vài câu trả lời ở đây thành một tập lệnh sử dụng CustomEvent để kích hoạt các sự kiện đã vuốt trong DOM. Thêm tập lệnh 0,7k swiped-event.min.js vào trang của bạn và lắng nghe các sự kiện đã vuốt :

vuốt sang trái

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

vuốt sang phải

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

vuốt lên

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

vuốt xuống

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

Bạn cũng có thể đính kèm trực tiếp vào một yếu tố:

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

Cấu hình tùy chọn

Bạn có thể chỉ định các thuộc tính sau để điều chỉnh cách các chức năng tương tác vuốt trong trang của bạn (đây là các tùy chọn) .

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

Mã nguồn có sẵn trên Github


Tôi đến đây vì thao tác vuốt thuần không hoạt động với tôi trên MOBILE
StefanBob

@StefanBob nếu bạn đánh dấu vào repo github với đủ thông tin để cho phép tôi tái tạo vấn đề, tôi sẽ xem xét vấn đề đó
John Doherty

1
Cảm ơn, nó hoạt động hoàn hảo! Tôi đã thay thế Hammer.js bằng thư viện của bạn, vì trước đây không hoạt động với thu phóng trình duyệt và đó là một vấn đề nghiêm trọng về khả năng sử dụng. Với thư viện này, chức năng thu phóng hoạt động chính xác (được thử nghiệm trên Android)
collimarco

15

những gì tôi đã sử dụng trước đây là bạn phải phát hiện sự kiện mousedown, ghi lại vị trí x, y của nó (tùy theo điều kiện nào) sau đó phát hiện sự kiện mouseup và trừ hai giá trị.


28
Tôi tin rằng đó là touchstart, touchmove, touchcelon và touchend mà người ta sẽ làm việc cùng, không phải mousedown hay mouseup.
Volomike


12

Tôi đã tìm thấy @givanse câu trả lời tuyệt vời là đáng tin cậy và tương thích nhất trên nhiều trình duyệt di động để đăng ký hành động vuốt.

Tuy nhiên, có một sự thay đổi trong mã của anh ta để làm cho nó hoạt động trong các trình duyệt di động hiện đại đang sử dụng jQuery.

event.touchessẽ không tồn tại nếu jQueryđược sử dụng và kết quả undefinedvà nên được thay thế bằng event.originalEvent.touches. Nếu không jQuery, event.touchesnên làm việc tốt.

Vì vậy, giải pháp trở thành,

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

Đã thử nghiệm trên:

  • Android : Trình duyệt Chrome, UC
  • iOS : Safari, Chrome, Trình duyệt UC

originalEvent là một thuộc tính JQuery. Nó thậm chí không tồn tại trong Javascript thuần túy.
Jan Derk

1
Theo câu trả lời SO này , một sự kiện chạm nếu được trình duyệt hỗ trợ sẽ được hiển thị thông qua event.originalEvent. Điều này event.touchesđã không còn tồn tại và kết quả là undefined.
nashcheez

event.touches chỉ ngừng tồn tại khi sử dụng JQuery. Hãy thử mã của bạn mà không có JQuery và bạn sẽ gặp lỗi mà evt.origenEvent không được xác định. JQuery hoàn toàn thay thế sự kiện bằng chính sự kiện của nó và đặt sự kiện trình duyệt riêng vào nguyên bản. Phiên bản ngắn: Mã của bạn chỉ hoạt động với JQuery. Nó hoạt động mà không có JQuery nếu bạn loại bỏ bản gốc.
Jan Derk

1
Vâng tôi đã nghiên cứu một chút và nhận ra bạn đã đúng về tính khả dụng của jquery event.originalEvent. Tôi sẽ cập nhật câu trả lời của tôi. Cảm ơn! :)
nashcheez


6

Một số mod của câu trả lời tối đa (không thể bình luận ...) để đối phó với các thao tác vuốt ngắn

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

6

thùng rác, vuốt hết thời gian, vuốtBlockElems thêm.

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

4

Nếu bất cứ ai đang cố gắng sử dụng jQuery Mobile trên Android và gặp vấn đề với phát hiện vuốt JQM

(Tôi đã có một số trên Xperia Z1, Galaxy S3, Nexus 4 và một số điện thoại Wiko nữa) điều này có thể hữu ích:

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

Vuốt trên Android không được phát hiện trừ khi đó là thao tác vuốt rất dài, chính xác và nhanh.

Với hai dòng này, nó hoạt động chính xác


Tôi cũng cần phải thêm: $.event.special.swipe.scrollSupressionThreshold = 8;nhưng bạn đặt tôi đi đúng hướng! Cảm ơn bạn!
Philip G

4

Tôi gặp sự cố với trình xử lý touchend bắn liên tục trong khi người dùng đang kéo một ngón tay xung quanh. Tôi không biết liệu đó có phải là do tôi làm sai hay không nhưng tôi đã viết lại phần này để tích lũy các bước di chuyển bằng touchmove và touchend thực sự kích hoạt cuộc gọi lại.

Tôi cũng cần phải có một số lượng lớn các trường hợp này và vì vậy tôi đã thêm các phương thức enable / vô hiệu hóa.

Và một ngưỡng mà một cú vuốt ngắn không bắn. Touchstart zero's quầy mỗi lần.

Bạn có thể thay đổi target_node khi đang bay. Cho phép tạo là tùy chọn.

/** 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);
            }
        }
    }
}

Đây là cho ES5 hay ES6?
1,21 gigawatt

@gigawatts Tôi không nhớ. Dự án đã sử dụng đã đạt EOL và tôi không cần mã kể từ đó. Tôi nghi ngờ vào thời điểm tôi đang viết cho ES6 nhưng đó là hơn 2 năm trước.
Xu hướng bắt đầu

3

Tôi cũng đã hợp nhất một vài câu trả lời, chủ yếu là câu đầu tiên và câu thứ hai với các lớp và đây là phiên bản của tôi:

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

Sau đó có thể sử dụng nó như sau:

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

Nó giúp tránh các lỗi bảng điều khiển khi bạn chỉ muốn gọi phương thức "onLeft".


2

Sử dụng hai:

jQuery mobile: hoạt động trong hầu hết các trường hợp và đặc biệt khi bạn đang phát triển applicaion sử dụng plugin jQuery khác thì tốt hơn nên sử dụng các điều khiển di động jQuery cho việc này. Truy cập tại đây: https://www.w3schools.com/jquerymobile/jquerymobile_events_touch.asp

Giờ búa! một trong những thư viện dựa trên javascript tốt nhất, nhẹ và nhanh. Truy cập tại đây: https://hammerjs.github.io/


2

Nếu bạn chỉ cần vuốt, tốt hơn hết là bạn chỉ nên sử dụng phần bạn cần. Điều này sẽ làm việc trên bất kỳ thiết bị cảm ứng.

Đây là ~ 450 byte 'sau khi nén gzip, thu nhỏ, babel, v.v.

Tôi đã viết lớp dưới đây dựa trên các câu trả lời khác, nó sử dụng tỷ lệ phần trăm được di chuyển thay vì pixel và một mẫu trình điều phối sự kiện để móc / không lấy các thứ.

Sử dụng nó như vậy:

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;


2

Tôi chỉ muốn phát hiện vuốt trái và phải, nhưng chỉ kích hoạt hành động khi sự kiện chạm kết thúc , vì vậy tôi đã sửa đổi một chút câu trả lời tuyệt vời của @ givanse để làm điều đó.

Tại sao phải làm điều đó? Ví dụ, trong khi vuốt, người dùng thông báo cuối cùng anh ta không muốn vuốt, anh ta có thể di chuyển ngón tay của mình ở vị trí ban đầu (một ứng dụng điện thoại "hẹn hò" rất phổ biến thực hiện điều này;)), và sau đó "vuốt sang phải" sự kiện bị hủy bỏ.

Vì vậy, để tránh sự kiện "vuốt phải" chỉ vì có chênh lệch 3px theo chiều ngang, tôi đã thêm một ngưỡng theo đó một sự kiện bị loại bỏ: để có sự kiện "vuốt phải", người dùng phải vuốt ít nhất 1/3 chiều rộng trình duyệt (tất nhiên bạn có thể sửa đổi điều này).

Tất cả những chi tiết nhỏ này nâng cao trải nghiệm người dùng. Đây là mã (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;
}

1

Ví dụ JS vanilla đơn giản để vuốt ngang:

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

Bạn có thể sử dụng logic khá giống nhau để vuốt dọc.


1

Thêm vào câu trả lời này ở đây . Điều này thêm hỗ trợ cho các sự kiện chuột để thử nghiệm trên máy tính để bàn:

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

0

Một ví dụ về cách sử dụng với 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]
}


Hiện đang sử dụng phiên bản này. Làm thế nào tôi có thể ngăn điều này bắn nhiều lần nếu bị lặp đi lặp lại? Tôi sử dụng tính năng này với tính năng hoạt hình cho hình thức sidecrolling và khi tôi vuốt nhiều lần, mọi thứ trở nên hơi khó hiểu và div của tôi bắt đầu chồng chéo trong khu vực có thể nhìn thấy.
NMALM

0

Bạn có thể có một thời gian dễ dàng hơn trước tiên thực hiện nó với các sự kiện chuột đến nguyên mẫu.

Có rất nhiều câu trả lời ở đây, bao gồm cả đầu, nên được sử dụng một cách thận trọng vì chúng không xem xét các trường hợp cạnh đặc biệt là xung quanh các hộp giới hạn.

Xem:

Bạn sẽ cần thử nghiệm để bắt các trường hợp và hành vi cạnh như con trỏ di chuyển bên ngoài phần tử trước khi kết thúc.

Vuốt là một cử chỉ rất cơ bản, đó là mức độ tương tác con trỏ giao diện cao hơn, xử lý gần như ngồi giữa xử lý các sự kiện thô và nhận dạng chữ viết tay.

Không có phương pháp chính xác duy nhất để phát hiện thao tác vuốt hoặc ném dù hầu như tất cả đều tuân theo nguyên tắc cơ bản là phát hiện chuyển động trên một phần tử có ngưỡng khoảng cách và tốc độ hoặc vận tốc. Bạn có thể chỉ cần nói rằng nếu có một chuyển động trên 65% kích thước màn hình theo một hướng nhất định trong một thời gian nhất định thì đó là một lần vuốt. Chính xác nơi bạn vẽ đường thẳng và cách bạn tính toán nó là tùy thuộc vào bạn.

Một số người cũng có thể nhìn nó từ góc độ của động lượng theo hướng và nó được đẩy ra khỏi màn hình bao xa khi phần tử được giải phóng. Điều này rõ ràng hơn với các lần vuốt dính mà phần tử có thể được kéo và sau đó khi phát hành sẽ bật trở lại hoặc bay khỏi màn hình như thể dây thun bị đứt.

Có lẽ lý tưởng để cố gắng tìm một thư viện cử chỉ mà bạn có thể chuyển hoặc sử dụng lại thường được sử dụng để thống nhất. Nhiều ví dụ ở đây là quá đơn giản, đăng ký một lần vuốt như một cú chạm nhẹ nhất theo bất kỳ hướng nào.

Android sẽ là sự lựa chọn rõ ràng mặc dù có vấn đề ngược lại, nó quá phức tạp.

Nhiều người dường như đã giải thích sai câu hỏi là bất kỳ chuyển động theo một hướng. Vuốt là một chuyển động rộng và tương đối ngắn áp đảo theo một hướng duy nhất (mặc dù có thể được trang bị và có các thuộc tính gia tốc nhất định). Một fling là tương tự mặc dù có ý định tình cờ đẩy một vật đi một khoảng cách hợp lý theo đà của chính nó.

Cả hai đều tương tự nhau đến mức một số thư viện chỉ có thể cung cấp fling hoặc vuốt, có thể được sử dụng thay thế cho nhau. Trên màn hình phẳng, thật khó để phân tách hai cử chỉ và nói chung mọi người đang thực hiện cả hai (vuốt màn hình vật lý nhưng vung yếu tố UI hiển thị trên màn hình).

Bạn lựa chọn tốt nhất là không tự làm. Hiện đã có một số lượng lớn các thư viện JavaScript để phát hiện các cử chỉ đơn giản .


0

Tôi đã làm lại giải pháp của @givanse để hoạt động như một cái móc React. Đầu vào là một số trình lắng nghe sự kiện tùy chọn, đầu ra là một ref chức năng (cần phải có chức năng để hook có thể chạy lại khi / nếu ref thay đổi).

Cũng được thêm vào trong tham số ngưỡng vuốt dọc / ngang, để các chuyển động nhỏ không vô tình kích hoạt người nghe sự kiện, nhưng chúng có thể được đặt thành 0 để bắt chước câu trả lời ban đầu chặt chẽ hơn.

Mẹo: để có hiệu suất tốt nhất, các chức năng nhập của trình lắng nghe sự kiện cần được ghi nhớ.

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);
};
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.