핀치를 감지하는 가장 간단한 방법


85

이것은 네이티브 앱이 아닌 WEB APP 입니다. Objective-C NS 명령을 사용하지 마십시오.

그래서 iOS에서 '핀치'이벤트를 감지해야합니다. 문제는 제스처 또는 멀티 터치 이벤트를 수행하기 위해 내가 보는 모든 플러그인 또는 방법이 (일반적으로) jQuery와 함께 있으며 태양 아래의 모든 제스처에 대한 전체 추가 플러그인입니다. 내 응용 프로그램은 거대하고 코드에서 데드 우드에 매우 민감합니다. 내가 필요한 것은 꼬집음을 감지하는 것이며 jGesture와 같은 것을 사용하는 것은 내 간단한 요구에 부풀어 오르는 방법입니다.

또한 핀치를 수동으로 감지하는 방법에 대한 이해가 제한적입니다. 나는 두 손가락의 위치를 ​​알 수 있지만 이것을 감지하기 위해 믹스를 올바르게 얻을 수없는 것 같습니다. 꼬집음을 감지하는 간단한 스 니펫이있는 사람이 있습니까?

답변:


71

당신은 사용하고자하는 gesturestart, gesturechange그리고 gestureend이벤트를 . 두 개 이상의 손가락이 화면을 터치 할 때마다 트리거됩니다.

핀치 제스처로 수행해야하는 작업에 따라 접근 방식을 조정해야합니다. scale승수는 사용자의 핀치 제스처가 얼마나 극적인 결정하기 위해 검토 할 수 있습니다. 참조 애플의 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에 관한 것이지만 질문 제목은 일반적으로 "핀치를 감지하는 가장 간단한 방법"입니다. gesturestart, gesturechange 및 gestureend 이벤트는 iOS에 따라 다르며 플랫폼 간 작동하지 않습니다. Android 또는 기타 터치 브라우저에서는 실행되지 않습니다. 이 교차 플랫폼을 수행하려면이 답변 stackoverflow.com/a/11183333/375690 과 같이 touchstart, touchmove 및 touchend 이벤트를 사용하십시오 .
Phil McCullick 2014 년

6
@phil 모든 모바일 브라우저를 지원하는 가장 간단한 방법을 찾고 있다면 hammer.js를 사용하는 것이 더 좋습니다
Dan Herbert

4
내가 jQuery를 사용 $(selector).on('gestureend',...)하고, 사용했다 e.originalEvent.scale대신 e.scale.
Chad von Nau 2014 년

3
@ChadvonNau 이는 jQuery의 이벤트 객체가 "정규화 된 W3C 이벤트 객체"이기 때문입니다. W3C 이벤트 객체는 인클루드하지 않는 scale속성을. 공급 업체별 속성입니다. 내 대답에는 vanilla JS로 작업을 수행하는 가장 간단한 방법이 포함되어 있지만 이미 JS 프레임 워크를 사용하고 있다면 훨씬 더 나은 API를 제공하므로 hammer.js를 사용하는 것이 좋습니다.
Dan Herbert

1
@superuberduper IE8 / 9는 핀치를 전혀 감지 할 방법이 없습니다. Touch API는 IE10까지 IE에 추가되지 않았습니다. 원래 질문은 iOS에 대해 구체적으로 물었지만 여러 브라우저에서이를 처리하려면 브라우저 간 차이점을 추상화하는 hammer.js 프레임 워크를 사용해야합니다.
Dan Herbert

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 웹킷의 기본 기능입니다. 두 손가락 스 와이프와 핀치의 차이를 구분할 수 없기 때문에 좋은 구현이 아닙니다. 좋은 조언이 아닙니다.
mmaclaurin 2012

34
@mmaclaurin 웹킷에는 항상 핀치 감지 기능이 없었기 때문에 (내가 틀렸다면 수정) 모든 터치 스크린이 웹킷을 사용하는 것은 아니며 때로는 스 와이프 이벤트를 감지 할 필요가 없습니다. OP는 데드 우드 라이브러리 기능이없는 간단한 솔루션을 원했습니다.
Jeffrey Sweeney 2011

6
OP는 iOS에 대해 언급했지만 다른 플랫폼을 고려할 때 가장 좋은 대답입니다. 거리 계산에서 제곱근 부분을 제외하고는 제외됩니다. 나는 그것을 넣었다.
undefined

3
@BrianMortenson 의도적이었습니다. sqrt비용이 많이들 수 있으며 일반적으로 손가락이 어느 정도 크기만큼 안팎으로 움직 였다는 사실 만 알면됩니다. 하지만 .. 나는 피타고라스의 정리라고했고, 나는 기술적으로 그것을 사용하지 않은)
제프리 스위니에게

2
@mmaclaurin 두 손가락 스 와이프가 아닌 모든 핀치 케이스를 감지하는 방식으로 (deltaX * deltaY <= 0) 확인하십시오.
Dolma

29

Hammer.js 끝까지! "변형"(핀치)을 처리합니다. http://eightmedia.github.com/hammer.js/

그러나 직접 구현하고 싶다면 Jeffrey의 대답이 꽤 확실하다고 생각합니다.


Dan의 대답을보기 전에 실제로 hammer.js를 찾아서 구현했습니다. 망치는 꽤 멋지다.
Fresheyeball

멋져 보였지만 데모는 그렇게 부드럽 지 않았습니다. 확대 한 다음 주위를 이동하려고하면 정말 버벅 거림이 느껴졌습니다.
Alex K

3
Hammer에는 뛰어난 버그가 많이 있으며 그중 일부는이 글을 쓰는 시점에서 매우 심각합니다 (특히 Android). 생각할 가치가 있습니다.
Single Entity

3
여기도 마찬가지, 버기. 해머를 시도했고 결국 Jeffrey의 솔루션을 사용했습니다.
Paul

4

불행히도 브라우저에서 핀치 제스처를 감지하는 것은 생각만큼 간단하지는 않지만 HammerJS를 사용하면 훨씬 쉽습니다!

HammerJS 데모로 핀치 줌 및 팬을 확인하십시오 . 이 예제는 Android, iOS 및 Windows Phone에서 테스트되었습니다.

HammerJSPinch Zoom and Pan 에서 소스 코드를 찾을 수 있습니다 .

편의를 위해 다음은 소스 코드입니다.

<!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>
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.