Einfachste Möglichkeit, eine Prise zu erkennen

84

Dies ist eine WEB- App, keine native App. Bitte keine Objective-C NS-Befehle.

Ich muss also "Pinch" -Ereignisse unter iOS erkennen. Das Problem ist, dass jedes Plugin oder jede Methode, die ich zum Ausführen von Gesten oder Multi-Touch-Ereignissen sehe, (normalerweise) mit jQuery funktioniert und ein zusätzliches Pluggin für jede Geste unter der Sonne ist. Meine Anwendung ist riesig und ich bin sehr empfindlich gegenüber Totholz in meinem Code. Alles, was ich brauche, ist, eine Prise zu erkennen, und die Verwendung von jGesture ist nur ein Weg, um meine einfachen Bedürfnisse zu befriedigen.

Außerdem habe ich ein begrenztes Verständnis dafür, wie eine Prise manuell erkannt wird. Ich kann die Position beider Finger bekommen, kann nicht die richtige Mischung finden, um dies zu erkennen. Hat jemand ein einfaches Snippet, das NUR eine Prise erkennt?

Fresheyeball
quelle

Antworten:

71

Sie möchten die verwenden gesturestart, gesturechangeund gestureendVeranstaltungen . Diese werden jedes Mal ausgelöst, wenn 2 oder mehr Finger den Bildschirm berühren.

Je nachdem, was Sie mit der Pinch-Geste tun müssen, muss Ihr Ansatz angepasst werden. Der scaleMultiplikator kann untersucht werden, um festzustellen, wie dramatisch die Quetschgeste des Benutzers war. Weitere Informationen zum Verhalten der scaleEigenschaft finden Sie in der TouchEvent-Dokumentation von Apple .

node.addEventListener('gestureend', function(e) {
    if (e.scale < 1.0) {
        // User moved fingers closer together
    } else if (e.scale > 1.0) {
        // User moved fingers further apart
    }
}, false);

Sie können das gesturechangeEreignis auch abfangen , um eine Prise zu erkennen, wenn dies erforderlich ist, damit sich Ihre App reaktionsschneller anfühlt.

Dan Herbert
quelle
58
Ich weiß, dass diese Frage speziell iOS betraf, aber der Titel der Frage lautet allgemein "Einfachster Weg, um eine Prise zu erkennen". Die Ereignisse "Gestenstart", "Gestenwechsel" und "Gestende" sind iOS-spezifisch und funktionieren nicht plattformübergreifend. Sie werden nicht auf Android oder anderen Touch-Browsern ausgelöst. Verwenden Sie dazu plattformübergreifend die Ereignisse touchstart, touchmove und touchend, wie in dieser Antwort stackoverflow.com/a/11183333/375690 .
Phil McCullick
6
@phil Wenn Sie nach der einfachsten Möglichkeit suchen, alle mobilen Browser zu unterstützen, sollten Sie hammer.js
Dan Herbert
4
Ich habe jQuery verwendet $(selector).on('gestureend',...)und musste e.originalEvent.scalestattdessen verwenden e.scale.
Chad von Nau
3
@ChadvonNau Das liegt daran, dass das Ereignisobjekt von jQuery ein "normalisiertes W3C-Ereignisobjekt" ist. Das W3C-Ereignisobjekt enthält die scaleEigenschaft nicht. Es ist eine herstellerspezifische Eigenschaft. Während meine Antwort den einfachsten Weg beinhaltet, die Aufgabe mit Vanilla JS zu erledigen, sollten Sie Hammer.js besser verwenden, wenn Sie bereits JS-Frameworks verwenden, da dies Ihnen eine viel bessere API bietet.
Dan Herbert
1
@superuberduper IE8 / 9 haben überhaupt keine Möglichkeit, eine Prise zu erkennen. Touch-APIs wurden dem IE erst mit IE10 hinzugefügt. Die ursprüngliche Frage wurde speziell zu iOS gestellt. Um dies jedoch browserübergreifend zu behandeln, sollten Sie das hammer.js-Framework verwenden, das die browserübergreifenden Unterschiede aufhebt.
Dan Herbert
133

Überlegen Sie, was ein pinchEreignis ist: zwei Finger auf einem Element, die sich aufeinander zu oder voneinander weg bewegen. Gestenereignisse sind meines Wissens ein ziemlich neuer Standard. Der wahrscheinlich sicherste Weg, dies zu tun, ist die Verwendung von Berührungsereignissen wie folgt:

( ontouchstartVeranstaltung)

if (e.touches.length === 2) {
    scaling = true;
    pinchStart(e);
}

( ontouchmoveVeranstaltung)

if (scaling) {
    pinchMove(e);
}

( ontouchendVeranstaltung)

if (scaling) {
    pinchEnd(e);
    scaling = false;
}

Verwenden Sie die hypotFunktion, um den Abstand zwischen den beiden Fingern zu ermitteln :

var dist = Math.hypot(
    e.touches[0].pageX - e.touches[1].pageX,
    e.touches[0].pageY - e.touches[1].pageY);
Jeffrey Sweeney
quelle
1
Warum würden Sie Ihre eigene Quetscherkennung schreiben? Dies ist eine native Funktionalität im iOS-Webkit. Dies ist auch keine gute Implementierung, da sie den Unterschied zwischen einem Wischen mit zwei Fingern und einer Prise nicht erkennen kann. Kein guter Rat.
mmaclaurin
34
@mmaclaurin, da das Webkit nicht immer eine Quetscherkennung hatte (korrigieren Sie mich, wenn ich falsch liege), verwenden nicht alle Touchscreens das Webkit und manchmal muss ein Wischereignis nicht erkannt werden. Das OP wollte eine einfache Lösung ohne Funktionen der Totholzbibliothek.
Jeffrey Sweeney
6
OP erwähnte iOS, aber dies ist die beste Antwort, wenn andere Plattformen in Betracht gezogen werden. Außer Sie haben den Quadratwurzelteil aus Ihrer Entfernungsberechnung herausgelassen. Ich habe es
undefiniert
3
@ BrianMortenson Das war beabsichtigt; sqrtkann teuer sein, und Sie müssen im Allgemeinen nur wissen, dass sich Ihre Finger um ein gewisses Maß hinein oder heraus bewegt haben. Aber ... ich habe pythagoreischen Satz gesagt, und ich habe ihn technisch nicht verwendet;)
Jeffrey Sweeney
2
@mmaclaurin Überprüfen Sie einfach, ob (deltaX * deltaY <= 0) auf diese Weise alle Quetschfälle und nicht den Zwei-Finger-Schlag erkennen.
Dolma
29

Hammer.js den ganzen Weg! Es behandelt "Transformationen" (Prisen). http://eightmedia.github.com/hammer.js/

Aber wenn Sie es selbst implementieren möchten, denke ich, dass Jeffreys Antwort ziemlich solide ist.

Bruno
quelle
Ich hatte gerade hammer.js gefunden und implementiert, bevor ich Dans Antwort sah. Hammer ist ziemlich cool.
Fresheyeball
Es sah cool aus, aber die Demos waren nicht so flüssig. Das Heranzoomen und das anschließende Schwenken fühlte sich wirklich ruckelig an.
Alex K
3
Es ist erwähnenswert, dass Hammer eine Menge ausstehender Fehler aufweist, von denen einige zum Zeitpunkt des Schreibens ziemlich schwerwiegend sind (insbesondere Android). Es lohnt sich nur darüber nachzudenken.
Single Entity
3
Gleich hier, Buggy. Versuchte Hammer, endete mit Jeffreys Lösung.
Paul
4

Leider ist das Erkennen von Quetschgesten in verschiedenen Browsern nicht so einfach, wie man es sich erhoffen würde, aber HammerJS macht es viel einfacher!

Schauen Sie sich die Demo "Pinch Zoom and Pan with HammerJS" an . Dieses Beispiel wurde auf Android, iOS und Windows Phone getestet.

Den Quellcode finden Sie unter Pinch Zoom and Pan with HammerJS .

Hier ist der Quellcode:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport"
        content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
  <title>Pinch Zoom</title>
</head>

<body>

  <div>

    <div style="height:150px;background-color:#eeeeee">
      Ignore this area. Space is needed to test on the iPhone simulator as pinch simulation on the
      iPhone simulator requires the target to be near the middle of the screen and we only respect
      touch events in the image area. This space is not needed in production.
    </div>

    <style>

      .pinch-zoom-container {
        overflow: hidden;
        height: 300px;
      }

      .pinch-zoom-image {
        width: 100%;
      }

    </style>

    <script src="https://hammerjs.github.io/dist/hammer.js"></script>

    <script>

      var MIN_SCALE = 1; // 1=scaling when first loaded
      var MAX_SCALE = 64;

      // HammerJS fires "pinch" and "pan" events that are cumulative in nature and not
      // deltas. Therefore, we need to store the "last" values of scale, x and y so that we can
      // adjust the UI accordingly. It isn't until the "pinchend" and "panend" events are received
      // that we can set the "last" values.

      // Our "raw" coordinates are not scaled. This allows us to only have to modify our stored
      // coordinates when the UI is updated. It also simplifies our calculations as these
      // coordinates are without respect to the current scale.

      var imgWidth = null;
      var imgHeight = null;
      var viewportWidth = null;
      var viewportHeight = null;
      var scale = null;
      var lastScale = null;
      var container = null;
      var img = null;
      var x = 0;
      var lastX = 0;
      var y = 0;
      var lastY = 0;
      var pinchCenter = null;

      // We need to disable the following event handlers so that the browser doesn't try to
      // automatically handle our image drag gestures.
      var disableImgEventHandlers = function () {
        var events = ['onclick', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover',
                      'onmouseup', 'ondblclick', 'onfocus', 'onblur'];

        events.forEach(function (event) {
          img[event] = function () {
            return false;
          };
        });
      };

      // Traverse the DOM to calculate the absolute position of an element
      var absolutePosition = function (el) {
        var x = 0,
          y = 0;

        while (el !== null) {
          x += el.offsetLeft;
          y += el.offsetTop;
          el = el.offsetParent;
        }

        return { x: x, y: y };
      };

      var restrictScale = function (scale) {
        if (scale < MIN_SCALE) {
          scale = MIN_SCALE;
        } else if (scale > MAX_SCALE) {
          scale = MAX_SCALE;
        }
        return scale;
      };

      var restrictRawPos = function (pos, viewportDim, imgDim) {
        if (pos < viewportDim/scale - imgDim) { // too far left/up?
          pos = viewportDim/scale - imgDim;
        } else if (pos > 0) { // too far right/down?
          pos = 0;
        }
        return pos;
      };

      var updateLastPos = function (deltaX, deltaY) {
        lastX = x;
        lastY = y;
      };

      var translate = function (deltaX, deltaY) {
        // We restrict to the min of the viewport width/height or current width/height as the
        // current width/height may be smaller than the viewport width/height

        var newX = restrictRawPos(lastX + deltaX/scale,
                                  Math.min(viewportWidth, curWidth), imgWidth);
        x = newX;
        img.style.marginLeft = Math.ceil(newX*scale) + 'px';

        var newY = restrictRawPos(lastY + deltaY/scale,
                                  Math.min(viewportHeight, curHeight), imgHeight);
        y = newY;
        img.style.marginTop = Math.ceil(newY*scale) + 'px';
      };

      var zoom = function (scaleBy) {
        scale = restrictScale(lastScale*scaleBy);

        curWidth = imgWidth*scale;
        curHeight = imgHeight*scale;

        img.style.width = Math.ceil(curWidth) + 'px';
        img.style.height = Math.ceil(curHeight) + 'px';

        // Adjust margins to make sure that we aren't out of bounds
        translate(0, 0);
      };

      var rawCenter = function (e) {
        var pos = absolutePosition(container);

        // We need to account for the scroll position
        var scrollLeft = window.pageXOffset ? window.pageXOffset : document.body.scrollLeft;
        var scrollTop = window.pageYOffset ? window.pageYOffset : document.body.scrollTop;

        var zoomX = -x + (e.center.x - pos.x + scrollLeft)/scale;
        var zoomY = -y + (e.center.y - pos.y + scrollTop)/scale;

        return { x: zoomX, y: zoomY };
      };

      var updateLastScale = function () {
        lastScale = scale;
      };

      var zoomAround = function (scaleBy, rawZoomX, rawZoomY, doNotUpdateLast) {
        // Zoom
        zoom(scaleBy);

        // New raw center of viewport
        var rawCenterX = -x + Math.min(viewportWidth, curWidth)/2/scale;
        var rawCenterY = -y + Math.min(viewportHeight, curHeight)/2/scale;

        // Delta
        var deltaX = (rawCenterX - rawZoomX)*scale;
        var deltaY = (rawCenterY - rawZoomY)*scale;

        // Translate back to zoom center
        translate(deltaX, deltaY);

        if (!doNotUpdateLast) {
          updateLastScale();
          updateLastPos();
        }
      };

      var zoomCenter = function (scaleBy) {
        // Center of viewport
        var zoomX = -x + Math.min(viewportWidth, curWidth)/2/scale;
        var zoomY = -y + Math.min(viewportHeight, curHeight)/2/scale;

        zoomAround(scaleBy, zoomX, zoomY);
      };

      var zoomIn = function () {
        zoomCenter(2);
      };

      var zoomOut = function () {
        zoomCenter(1/2);
      };

      var onLoad = function () {

        img = document.getElementById('pinch-zoom-image-id');
        container = img.parentElement;

        disableImgEventHandlers();

        imgWidth = img.width;
        imgHeight = img.height;
        viewportWidth = img.offsetWidth;
        scale = viewportWidth/imgWidth;
        lastScale = scale;
        viewportHeight = img.parentElement.offsetHeight;
        curWidth = imgWidth*scale;
        curHeight = imgHeight*scale;

        var hammer = new Hammer(container, {
          domEvents: true
        });

        hammer.get('pinch').set({
          enable: true
        });

        hammer.on('pan', function (e) {
          translate(e.deltaX, e.deltaY);
        });

        hammer.on('panend', function (e) {
          updateLastPos();
        });

        hammer.on('pinch', function (e) {

          // We only calculate the pinch center on the first pinch event as we want the center to
          // stay consistent during the entire pinch
          if (pinchCenter === null) {
            pinchCenter = rawCenter(e);
            var offsetX = pinchCenter.x*scale - (-x*scale + Math.min(viewportWidth, curWidth)/2);
            var offsetY = pinchCenter.y*scale - (-y*scale + Math.min(viewportHeight, curHeight)/2);
            pinchCenterOffset = { x: offsetX, y: offsetY };
          }

          // When the user pinch zooms, she/he expects the pinch center to remain in the same
          // relative location of the screen. To achieve this, the raw zoom center is calculated by
          // first storing the pinch center and the scaled offset to the current center of the
          // image. The new scale is then used to calculate the zoom center. This has the effect of
          // actually translating the zoom center on each pinch zoom event.
          var newScale = restrictScale(scale*e.scale);
          var zoomX = pinchCenter.x*newScale - pinchCenterOffset.x;
          var zoomY = pinchCenter.y*newScale - pinchCenterOffset.y;
          var zoomCenter = { x: zoomX/newScale, y: zoomY/newScale };

          zoomAround(e.scale, zoomCenter.x, zoomCenter.y, true);
        });

        hammer.on('pinchend', function (e) {
          updateLastScale();
          updateLastPos();
          pinchCenter = null;
        });

        hammer.on('doubletap', function (e) {
          var c = rawCenter(e);
          zoomAround(2, c.x, c.y);
        });

      };

    </script>

    <button onclick="zoomIn()">Zoom In</button>
    <button onclick="zoomOut()">Zoom Out</button>

    <div class="pinch-zoom-container">
      <img id="pinch-zoom-image-id" class="pinch-zoom-image" onload="onLoad()"
           src="https://hammerjs.github.io/assets/img/pano-1.jpg">
    </div>


  </div>

</body>
</html>

Redgeoff
quelle
3

Erkennen Sie zwei Finger, die auf jedes Element zoomen, einfach und ohne Probleme mit Bibliotheken von Drittanbietern wie Hammer.js (Vorsicht, Hammer hat Probleme mit dem Scrollen!)

function onScale(el, callback) {
    let hypo = undefined;

    el.addEventListener('touchmove', function(event) {
        if (event.targetTouches.length === 2) {
            let hypo1 = Math.hypot((event.targetTouches[0].pageX - event.targetTouches[1].pageX),
                (event.targetTouches[0].pageY - event.targetTouches[1].pageY));
            if (hypo === undefined) {
                hypo = hypo1;
            }
            callback(hypo1/hypo);
        }
    }, false);


    el.addEventListener('touchend', function(event) {
        hypo = undefined;
    }, false);
}
Andrey
quelle
Scheint besser zu sein event.touchesals event.targetTouches.
TheStoryCoder
1

Keine dieser Antworten erreichte das, wonach ich suchte, und so schrieb ich selbst etwas. Ich wollte ein Bild auf meiner Website mit meinem MacBookPro-Trackpad kneifen und zoomen. Der folgende Code (für den jQuery erforderlich ist) scheint zumindest in Chrome und Edge zu funktionieren. Vielleicht ist dies für jemand anderen von Nutzen.

function setupImageEnlargement(el)
{
    // "el" represents the image element, such as the results of document.getElementByd('image-id')
    var img = $(el);
    $(window, 'html', 'body').bind('scroll touchmove mousewheel', function(e)
    {
        //TODO: need to limit this to when the mouse is over the image in question

        //TODO: behavior not the same in Safari and FF, but seems to work in Edge and Chrome

        if (typeof e.originalEvent != 'undefined' && e.originalEvent != null
            && e.originalEvent.wheelDelta != 'undefined' && e.originalEvent.wheelDelta != null)
        {
            e.preventDefault();
            e.stopPropagation();
            console.log(e);
            if (e.originalEvent.wheelDelta > 0)
            {
                // zooming
                var newW = 1.1 * parseFloat(img.width());
                var newH = 1.1 * parseFloat(img.height());
                if (newW < el.naturalWidth && newH < el.naturalHeight)
                {
                    // Go ahead and zoom the image
                    //console.log('zooming the image');
                    img.css(
                    {
                        "width": newW + 'px',
                        "height": newH + 'px',
                        "max-width": newW + 'px',
                        "max-height": newH + 'px'
                    });
                }
                else
                {
                    // Make image as big as it gets
                    //console.log('making it as big as it gets');
                    img.css(
                    {
                        "width": el.naturalWidth + 'px',
                        "height": el.naturalHeight + 'px',
                        "max-width": el.naturalWidth + 'px',
                        "max-height": el.naturalHeight + 'px'
                    });
                }
            }
            else if (e.originalEvent.wheelDelta < 0)
            {
                // shrinking
                var newW = 0.9 * parseFloat(img.width());
                var newH = 0.9 * parseFloat(img.height());

                //TODO: I had added these data-attributes to the image onload.
                // They represent the original width and height of the image on the screen.
                // If your image is normally 100% width, you may need to change these values on resize.
                var origW = parseFloat(img.attr('data-startwidth'));
                var origH = parseFloat(img.attr('data-startheight'));

                if (newW > origW && newH > origH)
                {
                    // Go ahead and shrink the image
                    //console.log('shrinking the image');
                    img.css(
                    {
                        "width": newW + 'px',
                        "height": newH + 'px',
                        "max-width": newW + 'px',
                        "max-height": newH + 'px'
                    });
                }
                else
                {
                    // Make image as small as it gets
                    //console.log('making it as small as it gets');
                    // This restores the image to its original size. You may want
                    //to do this differently, like by removing the css instead of defining it.
                    img.css(
                    {
                        "width": origW + 'px',
                        "height": origH + 'px',
                        "max-width": origW + 'px',
                        "max-height": origH + 'px'
                    });
                }
            }
        }
    });
}
gcdev
quelle
0

Meine Antwort ist von Jeffreys Antwort inspiriert. Wenn diese Antwort eine abstraktere Lösung bietet, versuche ich, konkretere Schritte zur potenziellen Implementierung bereitzustellen. Dies ist lediglich eine Anleitung, die eleganter implementiert werden kann. Ein ausführlicheres Beispiel finden Sie in diesem Tutorial mit MDN-Webdokumenten.

HTML:

<div id="zoom_here">....</div>

JS

<script>
var dist1=0;
function start(ev) {
           if (ev.targetTouches.length == 2) {//check if two fingers touched screen
               dist1 = Math.hypot( //get rough estimate of distance between two fingers
                ev.touches[0].pageX - ev.touches[1].pageX,
                ev.touches[0].pageY - ev.touches[1].pageY);                  
           }
    
    }
    function move(ev) {
           if (ev.targetTouches.length == 2 && ev.changedTouches.length == 2) {
                 // Check if the two target touches are the same ones that started
               var dist2 = Math.hypot(//get rough estimate of new distance between fingers
                ev.touches[0].pageX - ev.touches[1].pageX,
                ev.touches[0].pageY - ev.touches[1].pageY);
                //alert(dist);
                if(dist1>dist2) {//if fingers are closer now than when they first touched screen, they are pinching
                  alert('zoom out');
                }
                if(dist1<dist2) {//if fingers are further apart than when they first touched the screen, they are making the zoomin gesture
                   alert('zoom in');
                }
           }
           
    }
        document.getElementById ('zoom_here').addEventListener ('touchstart', start, false);
        document.getElementById('zoom_here').addEventListener('touchmove', move, false);
</script>
Lazarus-CG
quelle