web-dev-qa-db-ja.com

タッチデバイスで左/右スワイプを検出しますが、上/下スクロールを許可します

左/右スワイプを検出して反応する必要がありますが、Xピクセルの最大上下運動で指を左/右のみに移動する限り、同じ要素をスクロールする機能をユーザーに提供したいです。 、スクロールしてはなりませんが、Xを超えるとスクロールするはずです。

だから私がやったことは:

var startX, startY, $this = $(this);
function touchmove(event) {
        var touches = event.originalEvent.touches;
        if (touches && touches.length) {
            var deltaX = touches[0].pageX - startX;
            var deltaY = touches[0].pageY - startY;
            if (Math.abs(deltaY) > 50) {
                $this.html('X: ' + deltaX + '<br> Y: ' + deltaY + '<br>TRUE');
                $this.unbind('touchmove', touchmove);
                return true;
            } else {
                $this.html('X: ' + deltaX + '<br> Y: ' + deltaY);
                event.preventDefault();
            }
        }
    }

    function touchstart(event) {
        var touches = event.originalEvent.touches;
        if (touches && touches.length) {
            startX = touches[0].pageX;
            startY = touches[0].pageY;
            $this.bind('touchmove', touchmove);
        }
        //event.preventDefault();
    }

しかし、「if」の場合にスクロールする機能を復元しません...

ヒントをありがとう。

29
Raphael Jeger

タッチハンドラーイベントを独自に作成しました。

以下をチェックします:

高速クリック:「fc」

左にスワイプ:「swl」

右にスワイプ:「swr」

上にスワイプ:「swu」

下にスワイプ: 'swd'

各チェックは、対応するイベントを初期化しますが、スクロールして、通常の操作を行うことができます。新しいイベントがいくつかあります。

swl swrが必要です。クリックイベントにはfc(fastclick)を使用することをお勧めします。通常のクリックよりもはるかに高速です。

window.onload = function() {
    (function(d) {
        var
            ce = function(e, n) {
                var a = document.createEvent("CustomEvent");
                a.initCustomEvent(n, true, true, e.target);
                e.target.dispatchEvent(a);
                a = null;
                return false
            },
            nm = true,
            sp = {
                x: 0,
                y: 0
            },
            ep = {
                x: 0,
                y: 0
            },
            touch = {
                touchstart: function(e) {
                    sp = {
                        x: e.touches[0].pageX,
                        y: e.touches[0].pageY
                    }
                },
                touchmove: function(e) {
                    nm = false;
                    ep = {
                        x: e.touches[0].pageX,
                        y: e.touches[0].pageY
                    }
                },
                touchend: function(e) {
                    if (nm) {
                        ce(e, 'fc')
                    } else {
                        var x = ep.x - sp.x,
                            xr = Math.abs(x),
                            y = ep.y - sp.y,
                            yr = Math.abs(y);
                        if (Math.max(xr, yr) > 20) {
                            ce(e, (xr > yr ? (x < 0 ? 'swl' : 'swr') : (y < 0 ? 'swu' : 'swd')))
                        }
                    };
                    nm = true
                },
                touchcancel: function(e) {
                    nm = false
                }
            };
        for (var a in touch) {
            d.addEventListener(a, touch[a], false);
        }
    })(document);
    //EXAMPLE OF USE
    var h = function(e) {
        console.log(e.type, e)
    };
    document.body.addEventListener('fc', h, false); // 0-50ms vs 500ms with normal click
    document.body.addEventListener('swl', h, false);
    document.body.addEventListener('swr', h, false);
    document.body.addEventListener('swu', h, false);
    document.body.addEventListener('swd', h, false);
}

この場合、hはすべてのタイプのイベントに対する私のハンドラーであり、ハンドラーを本文に追加します。

私があなたの質問を理解するためにあなたはただ書く必要があります

YOURELEMENT.addEventListener('swr',YOURSWIPERIGHTFUNCTION,false);
YOURELEMENT.addEventListener('swl',YOURSWIPELEFTFUNCTION,false);

複数の要素と同じ関数を処理するには... 1つのハンドラを追加するだけです。

あなたが持っているなら

<ul id="ul"><li>1</li><li>2</li><li>3</li></ul>

あなたがやる:

var deleteli=function(e){
    var li=e.target;
    console.log('deleting '+li.textContent);
}
document.getElementById('ul').addEventListener('swl',deleteli,false);

fc&swrでも同じ

iosにはバグがあります。alert()を使用しないでください。2回実行されます。

47
cocco

受け入れられた回答に「バグ」があります。 Chrome on Androidしかし、ブラウザまたは "webview"(html5-hybrid-appの場合)のビルドを使用しない場合、スワイプが検出されていません。

通常のスクロール動作のため、イベントは発生しません。 「e.preventDefault();」を追加しますtouchmoveでそれを修正するか、受け入れられた回答のEric Fullerからの修正があります。

これはすてきな切り抜きですが、モバイルWebAppまたはWebサイトでは、タッチイベントが常に観察されるため、スクロールのbad音が発生する可能性があります。

そこで、私は何か新しいものを作ることにしました。新しいイベントリスナを作成するほど快適ではありませんが、私のニーズに十分対応しており、パフォーマンスも優れています。

function detectswipe(el,func) {
  swipe_det = new Object();
  swipe_det.sX = 0;
  swipe_det.sY = 0;
  swipe_det.eX = 0;
  swipe_det.eY = 0;
  var min_x = 20;  //min x swipe for horizontal swipe
  var max_x = 40;  //max x difference for vertical swipe
  var min_y = 40;  //min y swipe for vertical swipe
  var max_y = 50;  //max y difference for horizontal swipe
  var direc = "";
  ele = document.getElementById(el);
  ele.addEventListener('touchstart',function(e){
    var t = e.touches[0];
    swipe_det.sX = t.screenX; 
    swipe_det.sY = t.screenY;
  },false);
  ele.addEventListener('touchmove',function(e){
    e.preventDefault();
    var t = e.touches[0];
    swipe_det.eX = t.screenX; 
    swipe_det.eY = t.screenY;    
  },false);
  ele.addEventListener('touchend',function(e){
    //horizontal detection
    if ((((swipe_det.eX - min_x > swipe_det.sX) || (swipe_det.eX + min_x < swipe_det.sX)) && ((swipe_det.eY < swipe_det.sY + max_y) && (swipe_det.sY > swipe_det.eY - max_y)))) {
      if(swipe_det.eX > swipe_det.sX) direc = "r";
      else direc = "l";
    }
    //vertical detection
    if ((((swipe_det.eY - min_y > swipe_det.sY) || (swipe_det.eY + min_y < swipe_det.sY)) && ((swipe_det.eX < swipe_det.sX + max_x) && (swipe_det.sX > swipe_det.eX - max_x)))) {
      if(swipe_det.eY > swipe_det.sY) direc = "d";
      else direc = "u";
    }

    if (direc != "") {
      if(typeof func == 'function') func(el,direc);
    }
    direc = "";
  },false);  
}

myfunction(el,d) {
  alert("you swiped on element with id '"+el+"' to "+d+" direction");
}

関数を使用するには、次のように使用します

detectswipe('an_element_id',myfunction);

detectswipe('an_other_element_id',my_other_function);

スワイプが検出されると、関数「myfunction」がパラメーターelement-idおよび「l、r、u、d」(左、右、上、下)で呼び出されます。

例: http://jsfiddle.net/rvuayqeo/1/

7
EscapeNetscape

これらすべてのコードには改善が必要です(タッチ操作で見つけることができるほとんどのコードと同様)。

タッチイベントで遊ぶときは、ユーザーが複数の指を持っている、タッチが識別子を持っている、そのtouchesリストは、表面上の現在のすべてのタッチ、移動していないタッチを表します

そのため、プロセスは比較的単純です。

  1. ontouchstart:最初に変更されたタッチを取得します(_event.originalEvent.touches_プロパティではなく、_event.originalEvent.changedTouches_ one)。その識別子を_event.originalEvent.changedTouches[0].identifier_に登録し、プロパティをタッチして(pageX/pageYまたはclientX/clientYを探します。 DOMElement.getBoundingClientRect()メソッド);

  2. ontouchmove:現在のタッチがevent.originalEvent.changedTouches.identifiedTouch( identifier )で変更されたタッチリストにあることを確認します。何も返されない場合は、ユーザーが別のタッチ(探しているタッチではない)を移動したことを意味します。また、タッチプロパティを登録して、必要な操作を探して実行します。

  3. ontouchend:繰り返しますが、現在のタッチがchangedTouchesリストにあることを確認する必要があります。タッチプロパティでジョブを実行し、最後に現在のタッチ識別子を破棄します。

もっと強くしたい場合は、複数のタッチ(1つだけでなく)を観察することを検討してください。

TouchEvent、TouchList、Touch onの詳細: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events

3
Loops

@coccoに触発されて、より良い(最小化されていない)バージョンを作成しました。

_(function(d) {
    // based on original source: https://stackoverflow.com/a/17567696/334451
    var newEvent = function(e, name) {
        // This style is already deprecated but very well supported in real world: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/initCustomEvent
        // in future we want to use CustomEvent function: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
        var a = document.createEvent("CustomEvent");
        a.initCustomEvent(name, true, true, e.target);
        e.target.dispatchEvent(a);
        a = null;
        return false
    };
    var debug = false; // emit info to JS console for all touch events?
    var active = false; // flag to tell if touchend should complete the gesture
    var min_gesture_length = 20; // minimum gesture length in pixels
    var tolerance = 0.3; // value 0 means pixel perfect movement up or down/left or right is required, 0.5 or more means any diagonal will do, values between can be tweaked

    var sp = { x: 0, y: 0, px: 0, py: 0 }; // start point
    var ep = { x: 0, y: 0, px: 0, py: 0 }; // end point
    var touch = {
        touchstart: function(e) {
            active = true;
            t = e.touches[0];
            sp = { x: t.screenX, y: t.screenY, px: t.pageX, py: t.pageY };
            ep = sp; // make sure we have a sensible end poin in case next event is touchend
            debug && console.log("start", sp);
        },
        touchmove: function(e) {
            if (e.touches.length > 1) {
                active = false;
                debug && console.log("aborting gesture because multiple touches detected");
                return;
            }
            t = e.touches[0];
            ep = { x: t.screenX, y: t.screenY, px: t.pageX, py: t.pageY };
            debug && console.log("move", ep, sp);
        },
        touchend: function(e) {
            if (!active)
                return;
            debug && console.log("end", ep, sp);
            var dx = Math.abs(ep.x - sp.x);
            var dy = Math.abs(ep.y - sp.y);

            if (Math.max(dx, dy) < min_gesture_length) {
                debug && console.log("ignoring short gesture");
                return; // too short gesture, ignore
            }

            if (dy > dx && dx/dy < tolerance && Math.abs(sp.py - ep.py) > min_gesture_length) { // up or down, ignore if page scrolled with touch
                newEvent(e, (ep.y - sp.y < 0 ? 'gesture-up' : 'gesture-down'));
                //e.cancelable && e.preventDefault();
            }
            else if (dx > dy && dy/dx < tolerance && Math.abs(sp.px - ep.px) > min_gesture_length) { // left or right, ignore if page scrolled with touch
                newEvent(e, (ep.x - sp.x < 0 ? 'gesture-left' : 'gesture-right'));
                //e.cancelable && e.preventDefault();
            }
            else {
                debug && console.log("ignoring diagonal gesture or scrolled content");
            }
            active = false;
        },
        touchcancel: function(e) {
            debug && console.log("cancelling gesture");
            active = false;
        }
    };
    for (var a in touch) {
        d.addEventListener(a, touch[a], false);
        // TODO: MSIE touch support: https://github.com/CamHenlin/TouchPolyfill
    }
})(window.document);
_

@coccoによる元のバージョンと比較した重要な変更:

  • 主要な情報源として_event.touches[0].screenX/screenY_を使用します。 _pageX/pageY_プロパティは、ページの一部がタッチでスクロールすると、_pageX/pageY_値にも影響するため、画面上のタッチの動きを正しく表しません。
  • 最小ジェスチャー長設定を追加
  • 斜めに近いジェスチャーを無視するための許容設定を追加
  • ページコンテンツがジェスチャーでスクロールした場合はジェスチャーを無視します(ジェスチャーをトリガーする前に_pageX/pageY_の違いを調べます)
  • ジェスチャー中に複数のタッチが行われた場合、ジェスチャーを中止する

将来行う必要があること:

  • CustomEvent()メソッドの代わりに createEvent() function interface を使用します。
  • mSIE互換性を追加する
  • 多分_pageX/pageY_とは別に_screenX/screenY_の最小ジェスチャー長を設定しますか?
  • タッチの動きが速すぎる場合、Chromeのスレッドスクロールは依然としてスクロール検出で問題を引き起こすようです。おそらく、イベントをトリガーする必要があるかどうかを決定する前に、スクロールがどこに行ったのかを決定する前に、次のフレームを待機しますか?

使用法は次のとおりです。

_document.body.addEventListener('gesture-right', function (e) {  ... });
_

またはjqueryスタイル

_$("article").on("gesture-down", function (e) { ... });
_
1

タッチが静止したままのときに左右を検出します。

これは、最後の位置を保存し、timeoutを使用して、touchmove停止後の最後の位置を消去することで行われます。

var currentX;
var lastX = 0;
var lastT;
$(document).bind('touchmove', function(e) {
    // If still moving clear last setTimeout
    clearTimeout(lastT);

    currentX = e.originalEvent.touches[0].clientX;

    // After stoping or first moving
    if(lastX == 0) {
        lastX = currentX;
    }

    if(currentX < lastX) {
        // Left
    } else if(currentX > lastX){
        // Right
    }

    // Save last position
    lastX = currentX;

    // Check if moving is done
    lastT = setTimeout(function() {
        lastX = 0;
    }, 100);
});
0
Nebojsa Sapic