检测夹点的最简单方法

Fre*_*all 72 javascript jquery mobile-safari ios

这是一个WEB APP而不是本机应用程序.请不要使用Objective-C NS命令.

所以我需要在iOS上检测"捏"事件.问题是我看到的用于做手势或多点触摸事件的每个插件或方法,(通常)使用jQuery,并且是针对太阳下每个手势的完整附加插件.我的应用程序非常庞大,我对代码中的枯木非常敏感.我所需要的只是检测一个捏,使用像jGesture这样的东西只是为了我的简单需求而膨胀.

另外,我对如何手动检测夹点的理解有限.我可以得到两个手指的位置,似乎无法正确地检测到这一点.有没有人有一个简单的片段,只能检测到捏?

Jef*_*ney 116

想想pinch事件是什么:两个手指放在一个元素上,彼此相向或相移.据我所知,手势事件是一个相当新的标准,所以最安全的方法是使用这样的触摸事件:

(ontouchstart事件)

if (e.touches.length === 2) {
    scaling = true;
    pinchStart(e);
}
Run Code Online (Sandbox Code Playgroud)

(ontouchmove事件)

if (scaling) {
    pinchMove(e);
}
Run Code Online (Sandbox Code Playgroud)

(ontouchend事件)

if (scaling) {
    pinchEnd(e);
    scaling = false;
}
Run Code Online (Sandbox Code Playgroud)

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

var dist = Math.hypot(
    e.touches[0].pageX - e.touches[1].pageX,
    e.touches[0].pageY - e.touches[1].pageY);
Run Code Online (Sandbox Code Playgroud)

  • @mmaclaurin因为webkit并不总是有夹点检测(如果我错了就纠正我),并非所有触摸屏都使用webkit,有时候不需要检测滑动事件.OP需要一个没有枯木库函数的简单解决方案. (30认同)
  • OP确实提到了iOS,但在考虑其他平台时,这是最好的答案.除非您将平方根部分从距离计算中移除.我把它放进去了. (6认同)
  • 为什么要编写自己的捏夹检测?这是 iOS webkit 中的本机功能。这也不是一个好的实现,因为它无法区分两指滑动和捏合之间的区别。这不是一个好建议。 (3认同)
  • @BrianMortenson那是故意的; `sqrt`可能很贵,而且你通常只需要知道你的手指移入或移出一定程度.但是......我确实说过毕达哥拉斯定理,而我在技术上并没有使用它;) (3认同)
  • @mmaclaurin 只需检查 (deltaX * deltaY <= 0) 是否以这种方式检测所有捏合情况而不是两指滑动。 (2认同)

Dan*_*ert 67

你想用的gesturestart,gesturechangegestureend事件.每当2个或更多手指触摸屏幕时,这些都会被触发.

根据您需要对捏合手势执行的操作,您的方法需要进行调整.该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);
Run Code Online (Sandbox Code Playgroud)

gesturechange如果您需要它来使您的应用程序感觉更具响应性,您还可以拦截事件以检测到捏合.

  • 我知道这个问题是关于iOS的,但问题标题是一般的"最简单的方法来检测捏".gesturestart,gesturechange和gestureend事件是iOS特定的,不能跨平台工作.它们不会在Android或任何其他触摸浏览器上触发.要做到这一点,跨平台使用touchstart,touchmove和touchend事件,如本答案http://stackoverflow.com/a/11183333/375690. (41认同)
  • @phil如果您正在寻找支持所有移动浏览器的最简单方法,那么最好使用hammer.js (6认同)
  • 我使用了 jQuery `$(selector).on('gestureend',...)`,并且不得不使用 `e.originalEvent.scale` 而不是 `e.scale`。 (3认同)
  • @ChadvonNau 那是因为 jQuery 的事件对象是“规范化的 W3C 事件对象”。[W3C 事件对象](http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html) 不包含 `scale` 属性。这是供应商特定的属性。虽然我的回答包括使用 vanilla JS 完成任务的最简单方法,但如果您已经在使用 JS 框架,那么最好使用hammer.js,因为它会为您提供更好的 API。 (3认同)

小智 27

Hammer.js一路走来!它处理"变换"(捏). http://eightmedia.github.com/hammer.js/

但是如果你想自己实现它,我认为杰弗里的答案非常可靠.

  • 值得一提的是,Hammer上有许多未解决的错误,其中一些在撰写本文时(尤其是Android)非常严重。值得考虑。 (2认同)
  • 这里也一样,越野车。尝试了Hammer,最终使用了Jeffrey的解决方案。 (2认同)

red*_*off 5

不幸的是,跨浏览器检测捏合手势并不像人们希望的那么简单,但是 HammerJS 让它变得更容易!

查看带有 HammerJS捏缩放和平移演示。此示例已在 Android、iOS 和 Windows Phone 上进行了测试。

您可以在Pinch Zoom and Pan with 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>
Run Code Online (Sandbox Code Playgroud)


And*_*rey 5

检测任何元素上的两个手指捏缩放,使用像 Hammer.js 这样的 3rd 方库轻松且没有麻烦(注意,锤子有滚动问题!)

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 似乎使用“event.touches”比“event.targetTouches”更好。 (2认同)