检测夹击的最简单方法

Simplest way to detect a pinch

本文关键字:最简单 方法 检测      更新时间:2023-09-26

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

所以我需要检测iOS上的"挤压"事件。问题是我看到的每个用于执行手势或多点触控事件的插件或方法,(通常)都带有jQuery,并且是阳光下每个手势的附加插件。我的应用程序很大,我对代码中的枯木非常敏感。我所需要的只是检测一个捏合,使用像jGesture这样的东西只是为了满足我的简单需求而臃肿的方法。

此外,我对如何手动检测捏合的理解有限。我可以得到两个手指的位置,似乎无法正确混合来检测这一点。有没有人有一个简单的片段可以检测夹紧?

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

使用 gesturestartgesturechangegestureend 事件。每当 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);

还可以截获 gesturechange 事件,以检测发生的收缩,如果你需要它来使应用感觉更灵敏。

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

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

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

查看使用 HammerJS 的 Pinch Zoom 和平移演示。此示例已在 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>

检测任何

元素上的两根手指捏缩放,轻松且没有像 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);
}

最简单的方法是响应"轮子"事件。

您需要调用ev.preventDefault()以防止浏览器执行全屏缩放。

浏览器合成"滚轮"事件以在触控板上进行捏合,作为奖励,您还可以处理鼠标滚轮事件。这是映射应用程序处理它的方式。

我示例中的更多详细信息:

let element = document.getElementById('el');
let scale = 1.0;
element.addEventListener('wheel', (ev) => {
  // This is crucial. Without it, the browser will do a full page zoom
  ev.preventDefault();
  // This is an empirically determined heuristic.
  // Unfortunately I don't know of any way to do this better.
  // Typical deltaY values from a trackpad pinch are under 1.0
  // Typical deltaY values from a mouse wheel are more than 100.
  let isPinch = Math.abs(ev.deltaY) < 50;
  if (isPinch) {
    // This is a pinch on a trackpad
    let factor = 1 - 0.01 * ev.deltaY;
    scale *= factor;
    element.innerText = `Pinch: scale is ${scale}`;
  } else {
    // This is a mouse wheel
    let strength = 1.4;
    let factor = ev.deltaY < 0 ? strength : 1.0 / strength;
    scale *= factor;
    element.innerText = `Mouse: scale is ${scale}`;
  }
});
<div id='el' style='width:400px; height:300px; background:#ffa'>
  Scale: 1.0
</div>

这些答案都没有达到我想要的,所以我最终自己写了一些东西。我想使用我的 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'
                    });
                }
            }
        }
    });
}

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

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

它与 Jeffrey Sweeney 的评论相同,如何在您的类中实现的完整示例。

this.touch.isPinch = false;
this.touc.pinchStart = 0;
this.touch.onTouchStart = (e) => {
   if (e.touches.length === 2) {
    this.touch.pinchStart = Math.hypot(e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY);
    this.touch.isScaling = true;
  }
}
this.touch.onTouchMove = (e) => {
    if (this.touch.isScaling) {
      const distance = Math.hypot(e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY);
      if (this.touch.pinchStart >= 200 && distance <= 90) this.touchPichOut(); //call function for pinchOut
      if (this.touch.pinchStart <= 100 && distance >= 280) this.touchPichIn(); //call function for pinchIn
    }
}
this.touch.onTouchCancel = (e) => {
   this.touch.isScaling = false;
}
this.touch.onTouchEnd = (e) => {
  if (this.touch.isScaling) this.touch.isScaling = false;
}

问候