MDN’s new design is in Beta! A sneak peek: https://blog.mozilla.org/opendesign/mdns-new-design-beta/

Тач-события

Перевод не завершен. Пожалуйста, помогите перевести эту статью с английского.

Чтобы предоставить качественную поддержку touch-based пользовательского интерфейса, тач-события предлагают возможность интерпретации воздействия пальца (или стилуса) на тач-экраны или трекболы.

Интерфейсы тач-событий являются относительно низкоуровневым API который может быть использован для поддержки приложений со специфическими мультитач взаимодействиями, например с жестом двух пальцев. Мультитач взаимодействие запускается когда палец (или стилус) впервые касается контакной поверхности. Другие пальцы могут затем нажать поверхность и опционально двигаться по ней. Взаимодействие заканчивается когда пальцы удаляются с поверхности. Во время взаимодействия, приложение получает тач события в начале, в ходе и конце фаз.

Тач-события подобны событиям мыши за исключением что они поддерживают одновременные касания и различные расположения на поверхности касания. Интерфейс TouchEvent инкапсулирует все точки касания которые сейчас активны. Интерфейс Touch, который представляет одну точку касания, включает информацию такую как позиция точки касания относительно viewport браузера. 

Определения

Поверхность (Surface)
Чувствительная к касаниям поверхность. Это может быть экран или трекпад.
Точка касания (Touch point)
Точка контакта с поверхностью. Это может быть палец (или локоть, ухо, нос, что угодно, но обычно это палец) или стилус. 

Интерфейсы

TouchEvent
Представляет событие которое происходит когда изменяется состояние касания на поверхности.
Touch
Представляет одну точку контакта между пользователем и поверхностью касания.
TouchList
Представляет группу касаний; это используется когда пользователь, например, касается поверхности несколькими пальцами одновременно.

Пример

В этом примере отслеживаются несколько касаний одновременно, позволяя пользователю рисовать в <canvas> несколькими пальцами одновременно. Это будет работать лишь в браузере, который поддерживает touch события.

Примечание: Текст ниже использует понятие "палец" при описании контакта с поверхностью, но это также может быть стилус или другой способ контакта.

Создай canvas

<canvas id="canvas" width="600" height="600" style="border:solid black 1px;">
  Ваш браузер не поддерживает элемент canvas.
</canvas>
<br>
<button onclick="startup()">Инициализировать</button>
<br>
Log: <pre id="log" style="border: 1px solid #ccc;"></pre>

Настройка обработчиков событий

После загрузки страницы, функция startup(), указанная ниже, должна вызываться в атрибуте onload элемента <body> (в примере мы используем кнопку для ее запуска, в связи с ограничениями MDN системы живых примеров).

function startup() {
  var el = document.getElementsByTagName("canvas")[0];
  el.addEventListener("touchstart", handleStart, false);
  el.addEventListener("touchend", handleEnd, false);
  el.addEventListener("touchcancel", handleCancel, false);
  el.addEventListener("touchmove", handleMove, false);
  log("initialized.");
}

Эта функция настраивает все обработчики событий для нашего элемента <canvas>, таким образом, мы сможем управлять touch событиями при их возникновении.

Отслеживание новых касаний

Рассмотрим касания в действии.

var ongoingTouches = new Array();

Когда возникает событие touchstart, свидетельствующее о новом касании к поверхности, вызывается рассматриваемая ниже функция handleStart().

function handleStart(evt) {
  evt.preventDefault();
  log("touchstart.");
  var el = document.getElementsByTagName("canvas")[0];
  var ctx = el.getContext("2d");
  var touches = evt.changedTouches;
        
  for (var i = 0; i < touches.length; i++) {
    log("touchstart:" + i + "...");
    ongoingTouches.push(copyTouch(touches[i]));
    var color = colorForTouch(touches[i]);
    ctx.beginPath();
    ctx.arc(touches[i].pageX, touches[i].pageY, 4, 0, 2 * Math.PI, false);  // a circle at the start
    ctx.fillStyle = color;
    ctx.fill();
    log("touchstart:" + i + ".");
  }
}

Она вызывает event.preventDefault() для того, чтобы предотвратить обработку браузером touch события (в том числе и событий мыши). Далее мы получаем контекст и вытягиваем список измененных точек касаний из свойства TouchEvent.changedTouches события.

После этого, мы перебираем все объекты Touch в списке, добавляя их в массив активных точек касаний и, рисуя начальную точку для рисунка в виде маленького круга, мы используем линию толщиной 4 пикселя, таким образом, получая аккуратный круг радиусом 4 пикселя.

Рисование как касательное движение

Каждый раз движение одного или более пальцев, определяется как событие  touchmove, в результаете вызывая функцию handleMove().В этом примере данная функция ответственна за обновление данных о касании и рисование линии от предыдущей позиции к текущей  каждого касания. 

function handleMove(evt) {
  evt.preventDefault();
  var el = document.getElementsByTagName("canvas")[0];
  var ctx = el.getContext("2d");
  var touches = evt.changedTouches;

  for (var i = 0; i < touches.length; i++) {
    var color = colorForTouch(touches[i]);
    var idx = ongoingTouchIndexById(touches[i].identifier);

    if (idx >= 0) {
      log("continuing touch "+idx);
      ctx.beginPath();
      log("ctx.moveTo(" + ongoingTouches[idx].pageX + ", " + ongoingTouches[idx].pageY + ");");
      ctx.moveTo(ongoingTouches[idx].pageX, ongoingTouches[idx].pageY);
      log("ctx.lineTo(" + touches[i].pageX + ", " + touches[i].pageY + ");");
      ctx.lineTo(touches[i].pageX, touches[i].pageY);
      ctx.lineWidth = 4;
      ctx.strokeStyle = color;
      ctx.stroke();

      ongoingTouches.splice(idx, 1, copyTouch(touches[i]));  // swap in the new touch record
      log(".");
    } else {
      log("can't figure out which touch to continue");
    }
  }
}

Этот цикл перебирает также измененные касания , но он просматривает в нашем массиве данных о касаниях предыдущие данные о каждом касании для определения начальной точки каждого отрезка линии, который должен быть нарисован с помощью касания.  Это реализовано путем отслеживания свойства Touch.identifier каждого касания. Это свойство является уникальным числом для каждого касания, и остается  постоянным в течении  всей длительности контакта пальца с экраном. 

Это позволяет нам получать координаты предыдущей позиции каждого касания и использовать подходящий контекстный метод, чтобы рисовать отрезок линии соединяя две позиции вмете.

После рисования линии, мы вызываем Array.splice()чтобы заменить предыдущие данные точке касания с текущей информацией в массиве ongoingTouches .

Обработка завершения касания

Когда пользователь убирает палец с крана, срабатывает событие touchend. Мы обрататываем его таким же способом: вызывая фунцию  handleEnd() , которая представлена ниже. Ее работа рисовать последний отрезок линии для каждого касания, которое завершилось, и удалять точку касания из списка текущих касаний.

function handleEnd(evt) {
  evt.preventDefault();
  log("touchend");
  var el = document.getElementsByTagName("canvas")[0];
  var ctx = el.getContext("2d");
  var touches = evt.changedTouches;

  for (var i = 0; i < touches.length; i++) {
    var color = colorForTouch(touches[i]);
    var idx = ongoingTouchIndexById(touches[i].identifier);

    if (idx >= 0) {
      ctx.lineWidth = 4;
      ctx.fillStyle = color;
      ctx.beginPath();
      ctx.moveTo(ongoingTouches[idx].pageX, ongoingTouches[idx].pageY);
      ctx.lineTo(touches[i].pageX, touches[i].pageY);
      ctx.fillRect(touches[i].pageX - 4, touches[i].pageY - 4, 8, 8);  // and a square at the end
      ongoingTouches.splice(idx, 1);  // remove it; we're done
    } else {
      log("can't figure out which touch to end");
    }
  }
}

Она очень похожа на предыдущюю функцию; единственная разница это то ,что мы рисуем маленький квадрат чтобы отметить конец и то, что мы вызываем  Array.splice(). Мы просто удаляем старые входы из списка текущих касаний без добавления обновленной информации. Результатом является окончание слежения за точкой касания.

Handling canceled touches

If the user's finger wanders into browser UI, or the touch otherwise needs to be canceled, the touchcancel event is sent, and we call the handleCancel() function below.

function handleCancel(evt) {
  evt.preventDefault();
  log("touchcancel.");
  var touches = evt.changedTouches;
  
  for (var i = 0; i < touches.length; i++) {
    ongoingTouches.splice(i, 1);  // remove it; we're done
  }
}

Since the idea is to immediately abort the touch, we simply remove it from the ongoing touch list without drawing a final line segment.

Convenience functions

This example uses two convenience functions that should be looked at briefly to help make the rest of the code more clear.

Selecting a color for each touch

In order to make each touch's drawing look different, the colorForTouch() function is used to pick a color based on the touch's unique identifier. This identifier is an opaque number, but we can at least rely on it differing between the currently-active touches.

function colorForTouch(touch) {
  var r = touch.identifier % 16;
  var g = Math.floor(touch.identifier / 3) % 16;
  var b = Math.floor(touch.identifier / 7) % 16;
  r = r.toString(16); // make it a hex digit
  g = g.toString(16); // make it a hex digit
  b = b.toString(16); // make it a hex digit
  var color = "#" + r + g + b;
  log("color for touch with identifier " + touch.identifier + " = " + color);
  return color;
}

The result from this function is a string that can be used when calling <canvas> functions to set drawing colors. For example, for a Touch.identifier value of 10, the resulting string is "#aaa".

Copying a touch object

Some browsers (mobile Safari, for one) re-use touch objects between events, so it's best to copy the bits you care about, rather than referencing the entire object.

function copyTouch(touch) {
  return { identifier: touch.identifier, pageX: touch.pageX, pageY: touch.pageY };
}

Finding an ongoing touch

The ongoingTouchIndexById() function below scans through the ongoingTouches array to find the touch matching the given identifier, then returns that touch's index into the array.

function ongoingTouchIndexById(idToFind) {
  for (var i = 0; i < ongoingTouches.length; i++) {
    var id = ongoingTouches[i].identifier;
    
    if (id == idToFind) {
      return i;
    }
  }
  return -1;    // not found
}

Showing what's going on

function log(msg) {
  var p = document.getElementById('log');
  p.innerHTML = msg + "\n" + p.innerHTML;
}

If your browser supports it, you can see it live.

jsFiddle example

Additional tips

This section provides additional tips on how to handle touch events in your web application.

Handling clicks

Since calling preventDefault() on a touchstart or the first touchmove event of a series prevents the corresponding mouse events from firing, it's common to call preventDefault() on touchmove rather than touchstart. That way, mouse events can still fire and things like links will continue to work. Alternatively, some frameworks have taken to refiring touch events as mouse events for this same purpose. (This example is oversimplified and may result in strange behavior. It is only intended as a guide.)

function onTouch(evt) {
  evt.preventDefault();
  if (evt.touches.length > 1 || (evt.type == "touchend" && evt.touches.length > 0))
    return;

  var newEvt = document.createEvent("MouseEvents");
  var type = null;
  var touch = null;

  switch (evt.type) {
    case "touchstart": 
      type = "mousedown";
      touch = evt.changedTouches[0];
      break;
    case "touchmove":
      type = "mousemove";
      touch = evt.changedTouches[0];
      break;
    case "touchend":        
      type = "mouseup";
      touch = evt.changedTouches[0];
      break;
  }

  newEvt.initMouseEvent(type, true, true, evt.originalTarget.ownerDocument.defaultView, 0,
    touch.screenX, touch.screenY, touch.clientX, touch.clientY,
    evt.ctrlKey, evt.altKey, evt.shiftKey, evt.metaKey, 0, null);
  evt.originalTarget.dispatchEvent(newEvt);
}

Calling preventDefault() only on a second touch

One technique for preventing things like pinchZoom on a page is to call preventDefault() on the second touch in a series. This behavior is not well defined in the touch events spec, and results in different behavior for different browsers (i.e., iOS will prevent zooming but still allow panning with both fingers; Android will allow zooming but not panning; Opera and Firefox currently prevent all panning and zooming.) Currently, it's not recommended to depend on any particular behavior in this case, but rather to depend on meta viewport to prevent zooming.

Specifications

Specification Status Comment
Touch Events – Level 2
Определение 'Touch' в этой спецификации.
Редакторский черновик Added radiusX, radiusY, rotationAngle, force properties
Touch Events
Определение 'Touch' в этой спецификации.
Рекомендация Initial definition.

Browser compatibility

Feature Chrome Firefox (Gecko) Internet Explorer Opera Safari
Basic support 22.0 18.0 (18.0)[1] Нет Нет Нет
Feature Android Android Webview Chrome for Android Firefox Mobile (Gecko) Firefox OS IE Mobile Opera Mobile Safari Mobile
Basic support (Да) (Да) (Да) 6.0 (6.0) (Да) 11 (Да) (Да)

[1] The dom.w3c_touch_events.enabled tri-state preference can be used to disable (0), enable (1), and auto-detect (2) support for standard touch events; by default, they're on auto-detect (2).

Prior to Gecko 12 (Firefox 12.0 / Thunderbird 12.0 / SeaMonkey 2.9), Gecko did not support multi-touch; only one touch at a time was reported.

As of Gecko 24.0 (Firefox 24.0 / Thunderbird 24.0 / SeaMonkey 2.21), the touch events support introduced with Gecko 18.0 (Firefox 18.0 / Thunderbird 18.0 / SeaMonkey 2.15) has been disabled on the desktop version of Firefox (баг 888304), as some popular sites including Google and Twitter were not working properly. Once the bug is fixed, the API will be enabled again. To enable it anyway, open about:config and set the dom.w3c_touch_events.enabled preference to 2. The mobile versions including Firefox for Android and Firefox OS are not affected by this change. Also, the API has been enabled on the Metro-style version of Firefox for Windows 8.

Prior to Gecko 6.0 (Firefox 6.0 / Thunderbird 6.0 / SeaMonkey 2.3), Gecko offered a proprietary touch event API. That API is now deprecated; you should switch to this one.

Метки документа и участники

 Внесли вклад в эту страницу: nboldar, vovchik-manko, surrsoft
 Обновлялась последний раз: nboldar,