Найпростіший спосіб виявити защемлення


85

Це веб- додаток, а не власний додаток. Будь ласка, жодних команд Objective-C NS.

Тож мені потрібно виявляти події «защемлення» на iOS. Проблема полягає в кожному плагіні або методі, який я бачу для жестів або мультитач-подій, є (як правило) з jQuery і є цілим додатковим плагіном для кожного жесту під сонцем. Моє додаток величезне, і я дуже чутливий до глухого дерева у своєму коді. Все, що мені потрібно, це виявити дрібку, і використання чогось на кшталт jGesture - це просто спосіб роздутися для моїх простих потреб.

Крім того, я маю обмежене розуміння того, як виявити дрібку вручну. Я можу визначити положення обох пальців, здається, не можу правильно зрозуміти суміш, щоб це виявити. Хтось має простий фрагмент, який ПРОСТО виявляє ущип?

Відповіді:


71

Ви хочете використовувати gesturestart, gesturechangeі gestureendподія . Вони спрацьовують щоразу, коли 2 або більше пальців торкаються екрана.

Залежно від того, що вам потрібно зробити за допомогою жесту, вам слід скорегувати ваш підхід. scaleМножник може бути досліджений , щоб визначити , наскільки драматично пинч жест користувача був. Див TouchEvent документації від Apple для отримання докладної інформації про те , як 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, але загальна назва запитання - "Найпростіший спосіб виявити дрібку". Події запуску жестів, обміну жестами та завершення жестів стосуються iOS і не працюють на різних платформах. Вони не запускатимуться на Android або будь-яких інших сенсорних браузерах. Для цього крос-платформа використовуйте події touchstart, touchmove та touchchend, як у цій відповіді stackoverflow.com/a/11183333/375690 .
Phil McCullick

6
@phil Якщо ви шукаєте найпростіший спосіб підтримати всі мобільні браузери, вам краще використовувати hammer.js
Ден Герберт,

4
Я використовував jQuery $(selector).on('gestureend',...)і повинен був використовувати e.originalEvent.scaleзамість e.scale.
Чад фон Нау

3
@ChadvonNau Це тому, що об'єкт події jQuery є "нормалізованим об'єктом події W3C". Об'єкт події W3C не включає scaleвластивість. Це властивість продавця. Хоча моя відповідь включає найпростіший спосіб виконати завдання з ванільним JS, якщо ви вже використовуєте JS фреймворки, вам було б краще використовувати hammer.js, оскільки він надасть вам набагато кращий API.
Dan Herbert

1
@superuberduper IE8 / 9 взагалі не може виявити дрібку. API Touch не були додані до IE до IE10. Оригінальне запитання було спеціально задане щодо iOS, але для обробки цього у всіх браузерах слід використовувати фреймворк hammer.js, який абстрагує різниці між браузерами.
Ден Герберт

135

Подумайте, що таке 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

34
@mmaclaurin, тому що у webkit не завжди було виявлення защемлення (виправте мене, якщо я помиляюся), не всі сенсорні екрани використовують webkit, і іноді не потрібно виявляти подію, яка проводиться пальцем. ОП бажало простого рішення без функцій бібліотеки мертвих дерев.
Джеффрі Суїні

6
OP згадував iOS, але це найкраща відповідь при розгляді інших платформ. За винятком того, що ви залишили частину квадратного кореня поза розрахунком відстані. Я вклав його.
undefined

3
@BrianMortenson Це було навмисно; sqrtможе коштувати дорого, і вам, як правило, потрібно лише знати, що ваші пальці рухалися вгору або назовні на якусь величину. Але .. Я сказав теорему Піфагора, і технічно не використовував її;)
Джеффрі Суїні,

2
@mmaclaurin Просто перевірте, чи (deltaX * deltaY <= 0) таким чином ви виявляєте всі випадки пощипування, а не два пальці.
Долма

29

Hammer.js до кінця! Він обробляє "перетворення" (щипки). http://eightmedia.github.com/hammer.js/

Але якщо ви хочете реалізувати це самостійно, я думаю, що відповідь Джеффрі досить тверда.


Я насправді щойно знайшов hammer.js і застосував його до того, як побачив відповідь Дана. Молоток досить крутий.
Fresheyeball

Це виглядало круто, але демо-версії були не такими гладкими. Масштабування, а потім спроба панорамування навколо відчувала себе по-справжньому неприємно.
Alex K

3
Варто зазначити, що Hammer має нагромаджене велике число помилок, деякі з яких є досить серйозними на момент написання цього (зокрема, Android). Просто варто подумати.
Single Entity

3
Те саме тут, баггі. Спробував Hammer, в підсумку застосував рішення Джеффрі.
Пол

4

На жаль, виявити жести притискання в браузерах не так просто, як можна було б сподіватися, але HammerJS значно полегшує це!

Ознайомтесь із Pinch Zoom і Pan з демонстрацією 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>


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

Мою відповідь надихнула відповідь Джеффрі. Там, де ця відповідь дає більш абстрактне рішення, я намагаюся надати більш конкретні кроки щодо того, як його потенційно реалізувати. Це просто керівництво, яке можна реалізувати більш елегантно. Для більш детального прикладу перегляньте цей посібник веб-документів 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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.