Cách đơn giản nhất để phát hiện một véo


85

Đây là một ỨNG DỤNG WEB không phải là một ứng dụng gốc. Vui lòng không có lệnh Objective-C NS.

Vì vậy, tôi cần phát hiện các sự kiện 'pinch' trên iOS. Vấn đề là mọi plugin hoặc phương pháp tôi thấy để thực hiện cử chỉ hoặc sự kiện đa chạm, (thường là) với jQuery và là một plugin bổ sung hoàn toàn cho mọi cử chỉ dưới ánh nắng mặt trời. Ứng dụng của tôi rất lớn và tôi rất nhạy cảm với deadwood trong mã của mình. Tất cả những gì tôi cần là phát hiện một điểm sơ hở và sử dụng một thứ gì đó như jGesture chỉ là cách để đáp ứng nhu cầu đơn giản của tôi.

Ngoài ra, tôi có một số hiểu biết hạn chế về cách phát hiện một véo bằng tay. Tôi có thể nhận được vị trí của cả hai ngón tay, dường như không thể trộn đúng để phát hiện điều này. Có ai có một đoạn mã đơn giản mà JUST phát hiện được không?

Câu trả lời:


71

Bạn muốn sử dụng gesturestart, gesturechangegestureendcác sự kiện . Chúng được kích hoạt bất cứ lúc nào 2 hoặc nhiều ngón tay chạm vào màn hình.

Tùy thuộc vào những gì bạn cần làm với cử chỉ véo, cách tiếp cận của bạn sẽ cần được điều chỉnh. Hệ scalesố có thể được kiểm tra để xác định mức độ ấn tượng của cử chỉ véo của người dùng. Xem tài liệu TouchEvent của Apple để biết chi tiết về cách thuộc scaletính sẽ hoạt động.

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

Bạn cũng có thể chặn gesturechangesự kiện để phát hiện sự cố khi nó xảy ra nếu bạn cần nó để làm cho ứng dụng của bạn phản hồi nhanh hơn.


58
Tôi biết câu hỏi này nói riêng về iOS nhưng tiêu đề câu hỏi chung chung là "Cách đơn giản nhất để phát hiện một lỗi". Các sự kiện bắt đầu bằng cử chỉ, thay đổi cử chỉ và kết thúc cử chỉ là các sự kiện dành riêng cho iOS và không hoạt động trên nhiều nền tảng. Chúng sẽ không kích hoạt trên Android hoặc bất kỳ trình duyệt cảm ứng nào khác. Để thực hiện nền tảng chéo này, hãy sử dụng các sự kiện touchstart, touchmove và touchhend, như trong câu trả lời stackoverflow.com/a/11183333/375690 này .
Phil McCullick

6
@phil Nếu bạn đang tìm kiếm những cách đơn giản nhất để hỗ trợ tất cả các trình duyệt di động, bạn nên sử dụng hammer.js
Dan Herbert

4
Tôi đã sử dụng jQuery $(selector).on('gestureend',...)và phải sử dụng e.originalEvent.scalethay thế e.scale.
Chad von Nau

3
@ChadvonNau Đó là vì đối tượng sự kiện của jQuery là "đối tượng sự kiện W3C chuẩn hóa". Các đối tượng W3C tổ chức sự kiện không bao gồm scalebất động sản. Đó là một tài sản cụ thể của nhà cung cấp. Mặc dù câu trả lời của tôi bao gồm cách đơn giản nhất để hoàn thành nhiệm vụ với vanilla JS, nhưng nếu bạn đã sử dụng các framework JS, bạn nên sử dụng hammer.js vì nó sẽ cung cấp cho bạn một API tốt hơn nhiều.
Dan Herbert

1
@superuberduper IE8 / 9 không có cách nào để phát hiện ra lỗi. API cảm ứng không được thêm vào IE cho đến IE10. Câu hỏi ban đầu hỏi cụ thể về iOS, nhưng để xử lý vấn đề này trên các trình duyệt, bạn nên sử dụng khung hammer.js để loại bỏ sự khác biệt giữa các trình duyệt.
Dan Herbert

134

Hãy nghĩ về một pinchsự kiện là gì: hai ngón tay trên một phần tử, di chuyển về phía hoặc ra xa nhau. Sự kiện cử chỉ, theo hiểu biết của tôi, là một tiêu chuẩn khá mới, vì vậy có lẽ cách an toàn nhất để thực hiện điều này là sử dụng các sự kiện chạm như vậy:

( ontouchstartsự kiện)

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

( ontouchmovesự kiện)

if (scaling) {
    pinchMove(e);
}

( ontouchendsự kiện)

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

Để có khoảng cách giữa hai ngón tay, hãy sử dụng hypotchức năng:

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

1
Tại sao bạn lại viết phát hiện nhúm của riêng mình? Đây là chức năng gốc trong bộ web dành cho iOS. Đây cũng không phải là một cách triển khai tốt vì nó không thể phân biệt được sự khác biệt giữa vuốt bằng hai ngón tay và vuốt. Không phải lời khuyên tốt.
mmaclaurin

34
@mmaclaurin vì webkit không phải lúc nào cũng có tính năng phát hiện chụm (hãy sửa lỗi cho tôi nếu tôi sai), không phải tất cả các màn hình cảm ứng đều sử dụng webkit và đôi khi sự kiện vuốt sẽ không cần được phát hiện. OP muốn có một giải pháp đơn giản mà không có các chức năng của thư viện deadwood.
Jeffrey Sweeney

6
OP đã đề cập đến iOS, nhưng đây là câu trả lời tốt nhất khi xem xét các nền tảng khác. Ngoại trừ bạn đã để phần căn bậc hai ra khỏi tính toán khoảng cách của bạn. Tôi đã đặt nó vào.
không xác định

3
@BrianMortenson Đó là cố ý; sqrtcó thể tốn kém và bạn thường chỉ cần biết rằng các ngón tay của bạn di chuyển vào hoặc ra ở một mức độ nào đó. Nhưng .. Tôi đã nói lý Pythagore, và tôi đã không về mặt kỹ thuật sử dụng nó;)
Jeffrey Sweeney

2
@mmaclaurin Chỉ cần kiểm tra xem (deltaX * deltaY <= 0) theo cách đó bạn có phát hiện tất cả các trường hợp chụm lại chứ không phải vuốt bằng hai ngón tay hay không.
Dolma

29

Hammer.js tất cả các cách! Nó xử lý các "biến đổi" (nhúm). http://eightmedia.github.com/hammer.js/

Nhưng nếu bạn muốn tự mình thực hiện nó, tôi nghĩ rằng câu trả lời của Jeffrey là khá chắc chắn.


Tôi thực sự vừa tìm thấy hammer.js và triển khai nó trước khi tôi thấy câu trả lời của Dan. Hammer là khá tuyệt.
Fresheyeball

Nó trông thật tuyệt, nhưng các bản demo không suôn sẻ như vậy. Phóng to và sau đó cố gắng quay xung quanh cảm thấy rất vui.
Alex K

3
Đáng chú ý là Hammer có rất nhiều lỗi nổi bật, một số lỗi khá nghiêm trọng tại thời điểm viết bài này (cụ thể là Android). Chỉ đáng để suy nghĩ về.
Thực thể độc thân

3
Ở đây cũng vậy, lỗi. Đã thử Hammer, cuối cùng sử dụng giải pháp của Jeffrey.
Paul

4

Thật không may, việc phát hiện các cử chỉ chụm trên các trình duyệt không đơn giản như người ta mong đợi, nhưng HammerJS làm cho nó dễ dàng hơn rất nhiều!

Hãy xem bản trình diễn Pinch Zoom và Pan bằng HammerJS . Ví dụ này đã được thử nghiệm trên Android, iOS và Windows Phone.

Bạn có thể tìm thấy mã nguồn tại Pinch Zoom and Pan bằng HammerJS .

Để thuận tiện cho bạn, đây là mã nguồn:

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

phát hiện thu phóng chụm hai ngón tay trên bất kỳ phần tử nào, dễ dàng và không gặp rắc rối với các lib bên thứ 3 như Hammer.js (hãy cẩn thận, búa có vấn đề với cuộn!)

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

Có vẻ như nó tốt hơn để sử dụng event.toucheshơn event.targetTouches.
TheStoryCoder

1

Không câu trả lời nào trong số này đạt được những gì tôi đang tìm kiếm, vì vậy tôi đã tự viết một cái gì đó. Tôi muốn thu phóng một hình ảnh trên trang web của mình bằng bàn di chuột MacBookPro. Mã sau (yêu cầu jQuery) dường như hoạt động trong Chrome và Edge. Có thể điều này sẽ được sử dụng cho người khác.

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

Câu trả lời của tôi được lấy cảm hứng từ câu trả lời của Jeffrey. Khi câu trả lời đó đưa ra một giải pháp trừu tượng hơn, tôi cố gắng cung cấp các bước cụ thể hơn về cách có thể triển khai nó. Đây chỉ đơn giản là một hướng dẫn, một hướng dẫn có thể được thực hiện một cách trang nhã hơn. Để có ví dụ chi tiết hơn, hãy xem hướng dẫn này của tài liệu web 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>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.