Il modo più semplice per rilevare un pizzico


87

Questa è una WEB APP, non un'app nativa. Per favore, nessun comando NS di Objective-C.

Quindi devo rilevare eventi di "pizzicamento" su iOS. Il problema è che ogni plugin o metodo che vedo per fare gesti o eventi multi-touch, è (di solito) con jQuery ed è un intero pluggin aggiuntivo per ogni gesto sotto il sole. La mia applicazione è enorme e sono molto sensibile al deadwood nel mio codice. Tutto ciò di cui ho bisogno è rilevare un pizzico e usare qualcosa come jGesture è solo un modo per gonfiarmi per le mie semplici esigenze.

Inoltre, ho una conoscenza limitata di come rilevare manualmente un pizzico. Posso ottenere la posizione di entrambe le dita, non riesco a ottenere il giusto mix per rilevarlo. Qualcuno ha un semplice frammento che rileva APPENA il pizzicamento?

Risposte:


71

Si desidera utilizzare i gesturestart, gesturechangeed gestureendeventi . Questi vengono attivati ​​ogni volta che 2 o più dita toccano lo schermo.

A seconda di cosa devi fare con il gesto di pizzicamento, il tuo approccio dovrà essere adattato. Il scalemoltiplicatore può essere esaminato per determinare quanto fosse drammatico il gesto di pizzicamento dell'utente. Consulta la documentazione TouchEvent di Apple per i dettagli su come scalesi comporterà la proprietà.

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

Puoi anche intercettare l' gesturechangeevento per rilevare un pizzicotto mentre accade se ne hai bisogno per rendere la tua app più reattiva.


59
So che questa domanda riguardava specificamente iOS, ma il titolo della domanda è generale "Il modo più semplice per rilevare un pizzico". Gli eventi inizio gestuale, cambio gestuale e fine gesto sono specifici di iOS e non funzionano su più piattaforme. Non si attiveranno su Android o altri browser touch. Per eseguire questa operazione multipiattaforma, utilizza gli eventi touchstart, touchmove e touchend, come in questa risposta stackoverflow.com/a/11183333/375690 .
Phil McCullick

6
@phil Se stai cercando il modo più semplice per supportare tutti i browser mobili, è meglio usare hammer.js
Dan Herbert,

4
Ho usato jQuery $(selector).on('gestureend',...)e ho dovuto usare al e.originalEvent.scaleposto di e.scale.
Chad von Nau

3
@ChadvonNau Questo perché l'oggetto evento di jQuery è un "oggetto evento W3C normalizzato". L' oggetto Evento W3C non include la scaleproprietà. È una proprietà specifica del fornitore. Sebbene la mia risposta includa il modo più semplice per svolgere l'attività con JS vanilla, se stai già utilizzando framework JS faresti meglio a usare hammer.js in quanto ti fornirà un'API molto migliore.
Dan Herbert,

1
@superuberduper IE8 / 9 non ha alcun modo per rilevare un pizzico. Le API touch non sono state aggiunte a IE fino a IE10. La domanda originale posta specificamente su iOS, ma per gestirla su tutti i browser dovresti usare il framework hammer.js che astrae le differenze tra browser.
Dan Herbert

136

Pensa a cos'è un pinchevento: due dita su un elemento, che si avvicinano o si allontanano l'una dall'altra. Gli eventi di gesti sono, per quanto ne so, uno standard abbastanza nuovo, quindi probabilmente il modo più sicuro per farlo è usare eventi di tocco in questo modo:

( ontouchstartevento)

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

( ontouchmoveevento)

if (scaling) {
    pinchMove(e);
}

( ontouchendevento)

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

Per ottenere la distanza tra le due dita, utilizzare la hypotfunzione:

var dist = Math.hypot(
    e.touches[0].pageX - e.touches[1].pageX,
    e.touches[0].pageY - e.touches[1].pageY);

1
Perché dovresti scrivere il tuo rilevamento pizzico? Questa è la funzionalità nativa nel webkit iOS. Anche questa non è una buona implementazione in quanto non può distinguere tra uno scorrimento con due dita e un pizzico. Non un buon consiglio.
mmaclaurin

34
@mmaclaurin perché webkit non ha sempre avuto il rilevamento del pizzicamento (correggimi se sbaglio), non tutti i touchscreen usano webkit e talvolta non è necessario rilevare un evento di scorrimento. L'OP voleva una soluzione semplice senza funzioni di libreria deadwood.
Jeffrey Sweeney

6
OP ha menzionato iOS, ma questa è la risposta migliore se si considerano altre piattaforme. Tranne che hai lasciato la parte della radice quadrata fuori dal calcolo della distanza. L'ho inserito.
undefined

3
@BrianMortenson Quello era intenzionale; sqrtpuò essere costoso e generalmente devi solo sapere che le tue dita si sono mosse dentro o fuori di una certa grandezza. Ma .. ho detto il teorema di Pitagora e tecnicamente non lo stavo usando;)
Jeffrey Sweeney

2
@mmaclaurin Controlla solo se (deltaX * deltaY <= 0) in questo modo rilevi tutti i casi di pizzicamento e non lo scorrimento con due dita.
Dolma

30

Hammer.js fino in fondo! Gestisce le "trasformazioni" (pizzichi). http://eightmedia.github.com/hammer.js/

Ma se desideri implementarlo da solo, penso che la risposta di Jeffrey sia piuttosto solida.


In realtà avevo appena trovato hammer.js e l'ho implementato prima di vedere la risposta di Dan. Hammer è piuttosto interessante.
Fresheyeball

Sembrava bello, ma le demo non erano così fluide. Zoomare e poi provare a fare una panoramica sembrava davvero strano.
Alex K

3
Vale la pena notare che Hammer ha un sacco di bug eccezionali, alcuni dei quali sono piuttosto gravi al momento della stesura di questo articolo (Android in particolare). Vale solo la pena pensarci.
Entità Unica

3
Lo stesso qui, buggy. Ho provato Hammer, ho finito per usare la soluzione di Jeffrey.
Paul,

4

Sfortunatamente, rilevare i gesti di pizzicamento sui browser non è così semplice come ci si aspetterebbe, ma HammerJS lo rende molto più semplice!

Controlla la Pinch Zoom e Pan con HammerJS demo . Questo esempio è stato testato su Android, iOS e Windows Phone.

Puoi trovare il codice sorgente su Pinch Zoom e Pan with HammerJS .

Per tua comodità, ecco il codice sorgente:

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


4

rilevare due dita pizzicare lo zoom su qualsiasi elemento, facile e senza problemi con librerie di terze parti come Hammer.js (attenzione, hammer ha problemi con lo scorrimento!)

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

1
Sembra che sia meglio usare event.touchesdi event.targetTouches.
TheStoryCoder

1

Nessuna di queste risposte ha ottenuto ciò che stavo cercando, quindi ho finito per scrivere qualcosa da solo. Volevo ingrandire un'immagine sul mio sito Web utilizzando il trackpad del MacBookPro. Il codice seguente (che richiede jQuery) sembra funzionare almeno in Chrome ed Edge. Forse questo sarà utile a qualcun altro.

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

0

La mia risposta è ispirata dalla risposta di Jeffrey. Laddove quella risposta fornisce una soluzione più astratta, cerco di fornire passaggi più concreti su come implementarla potenzialmente. Questa è semplicemente una guida, che può essere implementata in modo più elegante. Per un esempio più dettagliato, guarda questo tutorial di MDN web docs.

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>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.