web-dev-qa-db-ja.com

iPhoneおよびAndroid上のJavaScriptを介して指のスワイプを検出する

JavaScriptを使用してWebページ上でユーザーが指をある方向にスワイプしたことをどのように検出できますか?

私はiPhoneとAndroid携帯の両方のWebサイトに有効な解決策があるのではないかと思っていました。

227
827

単純なVanilla 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でテスト済み。

288
givanse

私は私の第一世代のiPod touchと私のドロイドの両方で素晴らしいこのjquery touchwipeプラグインを発見しました。 http://www.netcu.de/jquery-touchwipe-iphone-ipad-library

42
DaiLak

@ 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();
26
Marwelln

Hammer.jsを試しましたか? http://eightmedia.github.com/hammer.js/ Windows Phoneでも動作します。

18
Jorik

私が以前に使用したのは、mousedownイベントを検出し、そのx、y位置を記録し(そしてどちらか関係のある方)、そしてmouseupイベントを検出し、そして2つの値を減算する必要があるということです。

14
helloandre

jQuery Mobileにはスワイプサポートも含まれています。 http://api.jquerymobile.com/swipe/

$("#divId").on("swipe", function(event) {
    alert("It's a swipe!");
});
13
Grinn

ここでいくつかの答えを CustomEvent を使用してDOM内のスワイプイベントを起動するスクリプトにまとめました。 0.7k swiped-events.min.js スクリプトをあなたのページに加えて、swipedイベントを聞いてください。

左スワイプ

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 にあります。

9
John Doherty

スワイプ操作を登録するための@givanseの素晴らしい回答が、複数のモバイルブラウザで最も信頼性が高く互換性があることがわかりました。

しかし、jQueryを使用している現代のモバイルブラウザで動作させるために必要な彼のコードの変更があります。

jQueryが使用されている場合はevent.touchesは存在せず、その結果は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;                                             
};

テスト済み:

  • Android:Chrome、UCブラウザ
  • iOS:Safari、Chrome、UCブラウザ
8
nashcheez

短いjqueryプラグインとして TouchWipe を再パッケージ化しました。 detectSwipe

しきい値、タイムアウトスワイプ、スワイプBlockElemsを追加します。

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;
}
5
Sergey Guns

短いスワイプに対処するための最速の答えのいくつかのmod(コメントできません...)

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

誰かが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
    }

Android上でのスワイプは、それが非常に長く正確で速いスワイプでない限り検出されませんでした。

この2行では正しく動作します

4
Rayjax

ユーザーが指をドラッグしている間、タッチハンドラーのハンドラーが連続して発火するのに問題がありました。それが私が間違っていることによるものかどうかわからないが、touchmoveで動きを蓄積してtouchendが実際にコールバックを起動するようにこれを書き換えた。

また、これらのインスタンスを多数用意する必要があるため、有効化/無効化メソッドを追加しました。

そして短いスワイプが発射しない閾値。毎回タッチスタートゼロのカウンター。

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);
            }
        }
    }
}
3
Trendal Toews

使用される2つ:

jQuery mobile:はほとんどの場合、特に他のjQueryプラグインを使用するアプリケーションを開発している場合にはうまくいきます。ここにそれを訪問してください: https://www.w3schools.com/jquerymobile/jquerymobile_events_touch.asp

Hammer Time!最も軽量で高速なJavaScriptベースのライブラリの1つです。ここにそれを訪問してください: https://hammerjs.github.io/

2
Star

スワイプだけが必要な場合は、必要な部分だけを使用してサイズに関して賢明な方が良いです。これはどのタッチデバイスでも動作するはずです。

これは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;
1
Vargr

最初にマウスイベントを使用して実装し、プロトタイプを作成する方が簡単な場合があります。

ここには、特にバウンディングボックス周辺のエッジのケースを考慮しないため、トップを含む多くの回答がありますが、注意して使用する必要があります。

見る:

終了する前に、要素の外側に移動するポインターなどのEdgeのケースと動作をキャッチするために実験する必要があります。

スワイプは非常に基本的なジェスチャであり、生のイベントの処理と手書き認識の間にほぼ位置するインターフェイスポインターインタラクション処理の高レベルです。

スワイプやフリングを検出するための正確な方法はありませんが、ほぼすべてが、距離と速度または速度のしきい値を使用して、要素全体の動きを検出するという基本原則に従います。特定の時間内に特定の方向に画面サイズの65%を横切る動きがある場合、それはスワイプであると単純に言うことができます。正確に線を引く場所と計算方法はあなた次第です。

また、ある方向の運動量と、要素がリリースされたときにプッシュされたスクリーンからの距離の観点からそれを見るかもしれません。これは、要素をドラッグできるスティッキースワイプでより明確になり、その後リリースすると、弾性が壊れたように跳ね返るか、画面から飛び出します。

一貫性を保つために一般的に使用されている移植または再利用可能なジェスチャライブラリを見つけることは、おそらく理想的です。ここの例の多くは過度に単純化されており、スワイプをあらゆる方向へのわずかなタッチとして登録しています。

Android は明らかな選択ですが、反対の問題があります。それは過度に複雑です。

多くの人が質問をある方向への動きと誤解しているように見えます。スワイプは、単一の方向に圧倒的に広く比較的短い動きです(ただし、弧を描いて、特定の加速特性を持っている場合があります)。投げ飛ばしは似ていますが、アイテムをその勢いの下でかなりの距離をさりげなく推進するつもりです。

この2つは十分に類似しているため、一部のライブラリはフリングまたはスワイプのみを提供し、これらは互換的に使用できます。フラットスクリーンでは、2つのジェスチャを完全に分離することは難しく、一般的に話す人は両方を行います(物理的な画面をスワイプするが、画面に表示されるUI要素を飛ばす)。

最善の選択肢は、自分でやらないことです。 単純なジェスチャーを検出するための多数のJavaScriptライブラリ がすでにあります。

0
jgmjgm

水平スワイプのシンプルなバニラ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()
})

垂直方向のスワイプにもほぼ同じロジックを使用できます。

0
Damjan Pavlica

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

私はいくつかの答えもマージしました。大部分は最初のものと2番目のものはクラスです。そしてここに私のバージョンがあります:

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"メソッドだけを呼び出したいときにコンソールエラーを回避するのに役立ちます。

0