检测捏的最简单方法


85

这是一个WEB APP,不是本机应用。请不要使用Objective-C NS命令。

因此,我需要在iOS上检测“捏”事件。问题是我看到的用于做手势或多点触摸事件的每个插件或方法,(通常)是jQuery,并且是在阳光下每个手势的全部附加插件。我的应用程序非常庞大,并且我对代码中的死木非常敏感。我所需要的只是检测捏,使用jGesture之类的东西只是为了满足我的简单需求而肿。

此外,我对如何手动检测捏有有限的了解。我可以得到两个手指的位置,似乎无法正确地混合以检测到这一点。是否有人有一个简单的代码片段就能检测到捏?

Answers:


71

你想用的gesturestartgesturechangegestureend事件。只要两根或更多根手指触摸屏幕,它们就会被触发。

根据您需要用捏手势做的事情,您的方法需要进行调整。该scale乘法器可以进行检查,以确定用户的缩放手势如何戏剧性了。有关该属性的行为的详细信息,请参见Apple的TouchEvent文档scale

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

您也可以拦截该gesturechange事件,以便在需要时使应用感觉更灵敏,以检测出发生的干扰。


58
我知道这个问题是专门针对iOS的,但问题的标题是通用的“检测捏的最简单方法”。手势开始,手势更改和手势结束事件是特定于iOS的,并且不能跨平台使用。它们不会在Android或任何其他触摸浏览器上启动。为此,请使用touchstart,touchmove和touchend事件,例如在此答案stackoverflow.com/a/11183333/375690中
Phil McCullick 2014年

6
@phil如果您正在寻找支持所有移动浏览器的最简单方法,那么最好使用Hammer.js
Dan Herbert

4
我使用了jQuery $(selector).on('gestureend',...),不得不使用e.originalEvent.scale代替e.scale
乍得冯瑙2014年

3
@ChadvonNau那是因为jQuery的事件对象是“规范化的W3C事件对象”。在W3C事件对象不包括scale财产。这是供应商专有的属性。尽管我的回答包括使用Vanilla JS完成任务的最简单方法,但是如果您已经在使用JS框架,那么最好使用Hammer.js,因为它会为您提供更好的API。
Dan Herbert 2014年

1
@superuberduper IE8 / 9根本无法检测到挤压。直到IE10才将Touch API添加到IE。最初的问题专门询问有关iOS的问题,但要在各种浏览器中进行处理,您应该使用Hammer.js框架,该框架抽象出跨浏览器的差异。
丹·赫伯特

134

考虑一下pinch事件是什么:两个手指在一个元素上,彼此相对或相向移动。据我所知,手势事件是一个相当新的标准,因此可能最安全的方法是使用触摸事件,如下所示:

ontouchstart事件)

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

ontouchmove事件)

if (scaling) {
    pinchMove(e);
}

ontouchend事件)

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

要获取两个手指之间的距离,请使用以下hypot功能:

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

1
为什么要编写自己的捏检测?这是iOS Webkit中的本机功能。这也不是一个好的实现,因为它无法区分两指轻扫和捏之间的区别。不好的建议。
mmaclaurin 2012年

34
@mmaclaurin,因为webkit并不总是具有捏检测功能(如果我输入错了,请纠正我),不是所有的触摸屏都使用webkit,有时不需要检测滑动事件。OP希望提供一个没有沉木库功能的简单解决方案。
杰弗里·斯威尼

6
OP确实提到了iOS,但这是考虑其他平台时的最佳答案。除了将平方根部分留在距离计算之外。我把它英寸
未定义

3
@BrianMortenson那是故意的;sqrt可能会很昂贵,而且您通常只需要知道手指向内或向外移动一定程度即可。但是..我确实说过毕达哥拉斯定理,而我在技术上并没有使用它;)
Jeffrey Sweeney 2012年

2
@mmaclaurin只需检查是否(deltaX * deltaY <= 0)即可检测到所有夹伤情况,而不是两根手指轻扫。
2015年

29

Hammer.js一直!它处理“转换”(捏)。 http://eightmedia.github.com/hammer.js/

但是,如果您想自己实现它,我认为杰弗里的答案很可靠。


在看到Dan的答案之前,我实际上只是找到了Hammer.js并实现了它。锤子很酷。
Fresheyeball 2012年

看起来很酷,但是演示并不那么顺利。放大然后尝试四处移动时,感觉真的很糟糕。
Alex K

3
值得一提的是,Hammer上有许多未解决的错误,其中一些在编写本文时非常严重(尤其是Android)。值得考虑。
单一实体

3
这里也一样,越野车。尝试了Hammer,最终使用了Jeffrey的解决方案。
保罗

4

不幸的是,跨浏览器检测捏手势并不像人们希望的那样简单,但是HammerJS使其变得更加容易!

查看带有HammerJS演示Pinch Zoom和Pan。此示例已在Android,iOS和Windows Phone上进行了测试。

您可以使用HammerJS“缩放和平移”中找到源代码。

为了您的方便,以下是源代码:

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


3

检测到两根手指捏住任何元素,轻松缩放,并且没有Hammer.js之类的第三方库文件的麻烦(请注意,锤子滚动存在问题!)

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

似乎event.touches比更好event.targetTouches
TheStoryCoder

1

这些答案都没有达到我想要的,所以我自己写了一些东西。我想使用MacBookPro触控板缩小网站上的图像。以下代码(需要jQuery)似乎至少可以在Chrome和Edge中运行。也许这对其他人有用。

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

我的回答受到Jeffrey回答的启发。如果答案给出了更抽象的解决方案,那么我将尝试提供有关如何潜在实现的更具体的步骤。这只是一个指南,可以更优雅地实施。有关更详细的示例,请查看MDN网络文档的本教程

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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.