ChatGPT解决这个技术问题 Extra ChatGPT

Simplest way to detect a pinch

This is a WEB APP not a native app. Please no Objective-C NS commands.

So I need to detect 'pinch' events on iOS. Problem is every plugin or method I see for doing gestures or multi-touch events, is (usually) with jQuery and is a whole additional pluggin for every gesture under the sun. My application is huge, and I am very sensitive to deadwood in my code. All I need is to detect a pinch, and using something like jGesture is just way to bloated for my simple needs.

Additionally, I have a limited understanding of how to detect a pinch manually. I can get the position of both fingers, can't seem to get the mix right to detect this. Does anyone have a simple snippet that JUST detects pinch?

The best solution I found is in Mozilla's Docs. The Pinch Zoom Gesture article describes and solves this problematics very well.

F
Francisco

Think about what a pinch event is: two fingers on an element, moving toward or away from each other. Gesture events are, to my knowledge, a fairly new standard, so probably the safest way to go about this is to use touch events like so:

(ontouchstart event)

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

(ontouchmove event)

if (scaling) {
    pinchMove(e);
}

(ontouchend event)

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

To get the distance between the two fingers, use the hypot function:

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

Why would you write your own pinch detection? This is native functionality in iOS webkit. This is also not a good implementation as it can't tell the difference between a two-finger swipe and a pinch. Not good advice.
@mmaclaurin because webkit didn't always have pinch detection (correct me if I'm wrong), not all touchscreens use webkit, and sometimes a swipe event won't need to be detected. The OP wanted a simple solution without deadwood library functions.
OP did mention iOS, but this is the best answer when considering other platforms. Except you have left the square root part out of your distance calculation. I put it in.
@BrianMortenson That was intentional; sqrt can be expensive, and you generally only need to know that your fingers moved in or out by some magnitude. But.. I did say pythagorean theorem, and I wasn't technically using it ;)
@mmaclaurin Just check if (deltaX * deltaY <= 0) that way you detect all pinch cases and not the two finger swipe.
D
Dan Herbert

You want to use the gesturestart, gesturechange, and gestureend events. These get triggered any time 2 or more fingers touch the screen.

Depending on what you need to do with the pinch gesture, your approach will need to be adjusted. The scale multiplier can be examined to determine how dramatic the user's pinch gesture was. See Apple's TouchEvent documentation for details about how the scale property will behave.

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

You could also intercept the gesturechange event to detect a pinch as it happens if you need it to make your app feel more responsive.


I know this question was specifically about iOS but the question title is general "Simplest way to detect a pinch." The gesturestart, gesturechange, and gestureend events are iOS specific and do not work cross platform. They will not fire on Android or any other touch browsers. To do this cross platform use the touchstart, touchmove, and touchend events, like in this answer stackoverflow.com/a/11183333/375690.
@phil If you're looking for the simplest way to support all mobile browsers, you're better off using hammer.js
I used jQuery $(selector).on('gestureend',...), and had to use e.originalEvent.scale instead of e.scale.
@ChadvonNau That's because jQuery's event object is a "normalized W3C event object". The W3C Event object does not include the scale property. It's a vendor specific property. While my answer includes the simplest way to accomplish the task with vanilla JS, if you're already using JS frameworks you would be better off using hammer.js as it will provide you with a much better API.
@superuberduper IE8/9 have no way to detect a pinch at all. Touch APIs were not added to IE until IE10. The original question specifically asked about iOS, but to handle this across browsers you should use the hammer.js framework which abstracts away the cross-browser differences.
B
Bruno

Hammer.js all the way! It handles "transforms" (pinches). http://eightmedia.github.com/hammer.js/

But if you wish to implement it youself, i think that Jeffrey's answer is pretty solid.


I had actually just found hammer.js and implemented it before I saw Dan's answer. Hammer is pretty cool.
It looked cool, but the demos weren't that smooth. Zooming in and then trying to pan around felt really janky.
Worth noting that Hammer has a bucket load of outstanding bugs, some of which are quite severe at time of writing this (Android in particular). Just worth thinking about.
Same here, buggy. Tried Hammer, ended up using Jeffrey's solution.
Link looks like it is down as well
r
redgeoff

Unfortunately, detecting pinch gestures across browsers is a not as simple as one would hope, but HammerJS makes it a lot easier!

Check out the Pinch Zoom and Pan with HammerJS demo. This example has been tested on Android, iOS and Windows Phone.

You can find the source code at Pinch Zoom and Pan with HammerJS.

For your convenience, here is the source code:

Pinch Zoom

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.


S
Saman

detect two fingers pinch zoom on any element, easy and w/o hassle with 3rd party libs like Hammer.js (beware, hammer has issues with scrolling!)

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

Seems like it's better to use event.touches than event.targetTouches.
E
Ed_

The simplest way is to respond to the 'wheel' event.

You need to call ev.preventDefault() to prevent the browser from doing a full screen zoom.

Browsers synthesize the 'wheel' event for pinches on a trackpad, and as a bonus you also handle mouse wheel events. This is the way mapping applications handle it.

More details in my example:

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

Scale: 1.0


Thanks Ed_ for the abs()
Definitely the best answer. Thank you!!
The wheel event does not fire for mobile pinch zooms (I just tried). It does fire for trackpad zooming though.
g
gcdev

None of these answers achieved what I was looking for, so I wound up writing something myself. I wanted to pinch-zoom an image on my website using my MacBookPro trackpad. The following code (which requires jQuery) seems to work in Chrome and Edge, at least. Maybe this will be of use to someone else.

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

L
Lazarus-CG

My answer is inspired by Jeffrey's answer. Where that answer gives a more abstract solution, I try to provide more concrete steps on how to potentially implement it. This is simply a guide, one that can be implemented more elegantly. For a more detailed example check out this tutorial by MDN web docs.

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>

Adding the Zoom In and Zoom Out logic would be also very helpful.