iPhone 및 Android에서 JavaScript를 통해 손가락 스 와이프 감지


268

사용자가 JavaScript를 사용하여 웹 페이지에서 손가락을 어느 방향으로 쓸어 내었다는 것을 어떻게 알 수 있습니까?

iPhone과 Android 전화 모두에서 웹 사이트에 사용할 수있는 솔루션이 하나 있는지 궁금합니다.


1
스 와이프 인식을 위해서는 Hammer.js를 권장 합니다 . 그것은 아주 작은, 그리고 그것은 많은 제스처를 지원합니다 : - 출근 - 회전 - 핀치 - 보도 (홀드 기능) - 탭 - 팬
윌 Brickner

"touchmove"
Clay

@ Safari에서는 여전히 작동하지 않으므로 iPhone이 없습니다.
Jakuje

답변:


341

간단한 바닐라 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;                                             
};

Android에서 테스트되었습니다.


1
외모는,이 사건에 대한 지원이 무엇인지 어떤 생각을 냉각하고 간단한 touchstart, touchmove?
d.raev

1
꽤 잘 작동하지만 직선 운동을 감지하는 데 문제가 있습니다. 이 주제에 JQuery (desktop) 솔루션으로 해결 한 다른 답변을 게시 할 것입니다. 또한 이러한 스 와이프 이벤트의 마우스 버전을 추가하고 감도 옵션을 추가합니다.
Codebeat

1
제길. 주제가 닫혀서 답변을 추가 할 수 없습니다!
Codebeat

3
이것은 훌륭하게 작동하지만 왼쪽 / 오른쪽 및 위 / 아래는 뒤로 있습니다.
피터 아이젠 트라우트

4
originalEvent는 JQuery 속성입니다. JQuery없이 순수한 자바 스크립트를 실행하면 생략해야합니다. JQuery없이 실행하면 현재 코드에서 예외가 발생합니다.
Jan Derk

31

@ givanse의 답변을 바탕으로 다음과 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);
    }
}

다음과 같이 사용할 수 있습니다.

// 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
이 코드는 아마도 호출하려고 할 때 예외가 발생하기 때문에 작동하지 않을 것입니다 .bindhandleTouchMove 는 실제로 아무것도 반환하지 않았기 때문에 undefined 하지 않을 수 있습니다. 또한 함께 함수를 호출 할 때 바인딩 호출 쓸모 this.가 이미 현재 컨텍스트에 바인딩 있기 때문에
nick.skriabin

3
방금 제거 .bind(this);했는데 정상적으로 작동했습니다. 감사합니다 @nicholas_r
Ali Ghanavatian

일부는 요소를 가져옵니다 .document.getElementById ( 'my-element')에서 '#'을 제거하면 제대로 작동합니다. 감사합니다 @Marwelln :)
Blue Tram

4
당신이 슬쩍 끝날 때까지 대기 할 경우, 변경 (그들은 onMouseUp에 자신의 손가락을 들어 올리 이후 의미) touches[0]changedTouches[0]이벤트 핸들러 유형 handleTouchMove에를handleTouchEnd
TetraDev을

run()두 번 전화 하면 심한 메모리 누수가 발생합니다
Mat

20

여기에 몇 가지 답변을 CustomEvent 를 사용 하여 DOM에서 스 와이프 된 이벤트를 발생 시키는 스크립트로 병합했습니다 . 0.7k swiped-events.min.js 스크립트를 페이지에 추가하고 스 와이프 한 이벤트를 수신하십시오 .

왼쪽으로 스 와이프

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

오른쪽으로 스 와이프

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

스 와이프

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

스 와이프

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

요소에 직접 연결할 수도 있습니다.

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

선택적 구성

다음 속성을 지정하여 페이지에서 스 와이프 상호 작용이 작동하는 방식을 조정할 수 있습니다 (선택 사항) .

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

소스 코드는 Github에서 사용할 수 있습니다


순수 스 와이프가 모바일에서 작동하지 않기 때문에 여기에 왔습니다.
StefanBob

@StefanBob 만약 당신 이 문제를 재현 할 수 있도록 충분한 정보를 가지고 github 저장소 에 진드기를 올리면 , 나는 그것을 조사 할 것이다
John Doherty

1
고마워요, 완벽하게 작동합니다! 전자가 브라우저 확대 / 축소와 함께 작동하지 않아서 사용성 문제가 심각하기 때문에 Hammer.js를 라이브러리로 교체했습니다. 줌이 제대로 (안드로이드 테스트) 작품이 라이브러리로
collimarco

15

내가 전에 사용한 것은 mousedown 이벤트를 감지하고 x, y 위치를 기록한 다음 관련 이벤트를 감지하고 mouseup 이벤트를 감지하고 두 값을 빼야한다는 것입니다.


28
나는 터치 다운, 터치 이동, 터치 캔슬 및 터치 엔드가 마우스 다운이나 마우스 업이 아니라 작동한다고 생각합니다.
Volomike


12

@givanse의 훌륭한 답변이 스 와이프 동작을 등록하기 위해 여러 모바일 브라우저에서 가장 신뢰할 수 있고 호환되는 것으로 나타났습니다.

그러나을 사용하는 최신 모바일 브라우저에서 작동하도록 코드가 변경되었습니다 jQuery.

event.touchesjQuery사용하여 존재 undefined하고로 바뀌면 존재하지 않습니다 event.originalEvent.touches. 없이 jQuery, event.touches잘 작동합니다.

해결책은

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

에 테스트 :

  • 안드로이드 : 크롬, UC 브라우저
  • iOS : Safari, Chrome, UC 브라우저

originalEvent는 JQuery 속성입니다. 순수한 Javascript에는 존재하지 않습니다.
Jan Derk

1
이 SO 응답 브라우저에서 지원하는 터치 이벤트의 경우는 통해 노출됩니다 event.originalEvent. 것은이되어 event.touches지금 결과에 존재하는 것을 정지했다 undefined.
nashcheez

event.touches는 JQuery를 사용할 때만 중단되었습니다. JQuery없이 코드를 시도하면 evt.originalEvent가 정의되지 않았다는 오류가 발생합니다. JQuery는 이벤트를 자체 이벤트로 완전히 대체하고 기본 브라우저 이벤트를 원래 이벤트에 넣습니다. 짧은 버전 : 코드는 JQuery에서만 작동합니다. originalevent를 제거하면 JQuery없이 작동합니다.
Jan Derk

1
그래, 나는 약간의 연구를 통해 jquery enabled의 가용성에 대해 옳다는 것을 깨달았다 event.originalEvent. 답변을 업데이트하겠습니다. 감사! :)
nashcheez


6

짧은 스 와이프를 처리하기위한 uppest 답변 (댓글을 달 수 없습니다 ...)의 일부 모드

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

휴지통, 타임 아웃 스 와이프, 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;
}

4

누구나 Android에서 jQuery Mobile을 사용하려고하는데 JQM 스 와이프 감지에 문제가있는 경우

(Xperia Z1, Galaxy S3, Nexus 4 및 일부 Wiko 전화에도 사용 가능)이 유용 할 수 있습니다.

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

길고 정확하며 빠른 스 와이프가 아니면 안드로이드에서 스 와이프가 감지되지 않았습니다.

이 두 줄로 올바르게 작동합니다


나는 또한 덧붙일 필요가 있었다 : $.event.special.swipe.scrollSupressionThreshold = 8;그러나 당신은 나를 올바른 방향으로 놓았다! 감사합니다!
Philip G

4

사용자가 손가락을 드래그하는 동안 터치 엔드 핸들러가 계속 발사되는 데 문제가있었습니다. 그게 내가 잘못하고있는 일인지 아닌지 모르겠지만 touchmove와 함께 이동을 축적하기 위해 이것을 다시 배선하고 touchend는 실제로 콜백을 발생시킵니다.

또한 이러한 인스턴스가 많이 필요했기 때문에 활성화 / 비활성화 방법을 추가했습니다.

짧은 스 와이프가 실행되지 않는 임계 값입니다. Touchstart는 매번 카운터를 0으로 만듭니다.

target_node를 즉시 변경할 수 있습니다. 생성시 활성화는 선택 사항입니다.

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

ES5 또는 ES6입니까?
1.21 기가 와트

기억 나지 않는 @gigawatts. 사용 된 프로젝트는 이미 EOL에 도달했으며 이후 코드가 필요하지 않았습니다. ES6를 쓸 당시에는 2 년이 지났을 것으로 생각됩니다.
트렌디 한 견인

3

나는 대부분의 답변을 클래스와 병합했으며 여기에 내 버전이 있습니다.

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

나중에 다음과 같이 사용할 수 있습니다.

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

"onLeft"메소드 만 호출하려고 할 때 콘솔 오류를 피하는 데 도움이됩니다.



2

살짝 밀기 만하면 필요한 부분 만 사용하는 것이 현명합니다. 모든 터치 장치에서 작동합니다.

이것은 gzip 압축, 축소, babel 등 후 ~ 450 바이트입니다.

다른 답변을 기반으로 아래 클래스를 작성했습니다. 픽셀 대신 이동 비율과 이벤트 디스패처 패턴을 사용하여 사물을 연결 / 해제합니다.

다음과 같이 사용하십시오.

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

왼쪽 및 오른쪽 스 와이프 만 감지하고 싶지만 터치 이벤트가 끝날 때만 동작을 트리거했습니다. 하므로 @givanse의 큰 대답을 약간 수정했습니다.

왜 그렇게해야합니까? 예를 들어, 스 와이프하는 동안 사용자가 마지막으로 스 와이프하고 싶지 않다는 것을 알게 되면 손가락을 원래 위치로 움직일 수 있습니다 (매우 인기있는 "데이트"전화 응용 프로그램이이를 수행합니다)). 이벤트가 취소되었습니다.

따라서 가로로 3 픽셀의 차이가 있기 때문에 "오른쪽으로 스 와이프"이벤트를 피하기 위해 이벤트를 버리는 임계 값을 추가했습니다. "오른쪽으로 스 와이프"이벤트를 가지려면 사용자가 최소한 스 와이프해야합니다. 브라우저 너비의 1/3 (물론이를 수정할 수 있음)

이 모든 작은 세부 사항은 사용자 경험을 향상시킵니다. 다음은 (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

가로 스 와이프에 대한 간단한 바닐라 JS 예 :

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

세로 스 와이프에는 거의 같은 논리를 사용할 수 있습니다.


1

이 답변을 여기에 추가 하십시오 . 이것은 데스크탑에서 테스트하기 위해 마우스 이벤트에 대한 지원을 추가합니다.

<!--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

오프셋과 함께 사용하는 방법의 예입니다.

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


현재이 버전을 사용하고 있습니다. 반복적으로 스 와이프하면 여러 번 실행되는 것을 어떻게 방지 할 수 있습니까? 사이드 스크롤링 양식에 애니메이션 기능을 사용하여 이것을 활용하고 여러 번 스 와이프하면 약간 까다로워지고 div가 보이는 영역에서 겹치기 시작합니다.
NMALM

0

마우스 이벤트로 프로토 타입을 작성하여 구현하는 것이 더 쉬울 것입니다.

상단을 포함하여 여기에 많은 답변이 있습니다. 특히 경계 상자와 관련된 가장자리 사례를 고려하지 않으므로주의해서 사용해야합니다.

보다:

종료하기 전에 포인터가 요소 외부로 이동하는 등의 경우와 동작을 잡으려고 실험해야합니다.

스 와이프는 기본 이벤트 처리와 필기 인식 사이에 대략적으로 위치한 상위 수준의 인터페이스 포인터 상호 작용 처리 인 매우 기본적인 제스처입니다.

스 와이프 또는 플링을 감지하는 정확한 방법은 하나도 없지만 사실상 거의 모두 거리와 속도 또는 속도의 임계 값으로 요소를 가로 지르는 동작을 감지하는 기본 원칙을 따릅니다. 주어진 시간 내에 주어진 방향으로 화면 크기의 65 %를 가로 질러 이동하는 경우 스 와이프라고 간단히 말할 수 있습니다. 정확하게 선을 그리는 위치와 계산 방법은 전적으로 귀하에게 달려 있습니다.

어떤 이들은 운동량의 관점에서 방향을보고 요소를 놓을 때 화면에서 얼마나 멀리 밀려 났는지 볼 수도 있습니다. 요소를 드래그 할 수있는 끈적 거리는 스 와이프를 사용하면 더 명확 해집니다.

일관성에 일반적으로 사용되는 포팅 또는 재사용이 가능한 제스처 라이브러리를 찾는 것이 이상적입니다. 여기의 많은 예제는 스 와이프를 어느 방향 으로든 가장 작은 터치로 등록하여 지나치게 단순합니다.

반대의 문제가 있지만 안드로이드 는 명백한 선택이 될 것입니다. 매우 복잡합니다.

많은 사람들이이 질문을 한 방향으로의 움직임으로 잘못 해석 한 것으로 보입니다. 스 와이프는 단일 방향으로 압도적으로 넓고 비교적 짧은 동작입니다 (아크가 있고 특정 가속 특성을 가질 수 있음). 플링은 비슷하지만 아이템을 자연스럽게 자신의 운동량 아래에서 공평한 거리로 밀어 내려고합니다.

두 라이브러리는 일부 라이브러리가 플링 또는 스 와이프 만 제공 할 수 있다는 점에서 충분히 유사합니다. 플랫 스크린에서는 두 제스처를 진정으로 분리하기가 어렵고 일반적으로 사람들이 두 가지 작업을 모두 수행하고 있습니다 (실제 스크린을 스 와이프하지만 화면에 표시된 UI 요소를 튕기는 경우).

가장 좋은 방법은 직접하지 않는 것입니다. 간단한 제스처를 감지하기위한 많은 수의 JavaScript 라이브러리 가 이미 있습니다 .


0

@givanse 의 솔루션을 재 작업 했습니다. 을 React 후크로 작동하도록 . 입력은 선택적인 이벤트 리스너이며 출력은 기능적인 참조입니다 (참조가 변경 될 때 후크가 재실행 될 수 있도록 기능적이어야 함).

또한 세로 / 가로 스 와이프 임계 값 매개 변수에 추가되어 작은 동작으로 인해 이벤트 리스너가 실수로 트리거되지는 않지만 원래 답변을 더 가깝게 모방하도록 0으로 설정할 수 있습니다.

팁 : 최상의 성능을 위해서는 이벤트 리스너 입력 기능을 메모 해야합니다.

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);
};
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.