mozilla

Revision 347423 of Eventos de toque

  • Enlace amigable (slug) de la revisión: DOM/Touch_events
  • Título de la revisión: Touch events
  • Id de la revisión: 347423
  • Creada:
  • Creador: maedca
  • ¿Es la revisión actual? No
  • Comentario

Contenido de la revisión

{{ DevDerbyPromoHeader("touch events") }}

In order to provide quality support for touch-based user interfaces, touch events offer the ability to interpret finger activity on touch screens or trackpads.

Definitions

Surface
The touch-sensitive surface. This may be a screen or trackpad.
Touch point
A point of contact with the surface. This may be a finger (or elbow, ear, nose, whatever, but probably a finger) or stylus.

Interfaces

{{ domxref("TouchEvent") }}
Represents an event that occurs when the state of touches on the surface changes.
{{ domxref("Touch") }}
Represents a single point of contact between the user and the touch surface.
{{ domxref("TouchList") }}
Represents a group of touches; this is used when the user has, for example, multiple fingers on the surface at the same time.
{{ domxref("DocumentTouch") }}
Contains convenience methods for creating {{ domxref("Touch") }} and {{ domxref("TouchList") }} objects.

Example

This example tracks multiple touch points at a time, allowing the user to draw in a {{ HTMLElement("canvas") }} with more than one finger at a time. It will only work on a browser that supports touch events.

Note: The text below uses the term "finger" when describing the contact with the surface, but it could, of course, also be a stylus or other contact method.

Setting up the event handlers

When the page loads, the startup() function shown below is called by our {{ HTMLElement("body") }} element's onload attribute.

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("touchleave", handleLeave, false);
  el.addEventListener("touchmove", handleMove, false);
}

This simply sets up all the event listeners for our {{ HTMLElement("canvas") }} element so we can handle the touch events as they occur.

Tracking new touches

When a touchstart event occurs, indicating that a new touch on the surface has occurred, the handleStart() function below is called.

function handleStart(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++) {
    ongoingTouches.push(touches[i]);
    var color = colorForTouch(touches[i]);
    ctx.fillStyle = color;
    ctx.fillRect(touches[i].pageX-2, touches[i].pageY-2, 4, 4);
  }
}

This calls {{ domxref("event.preventDefault()") }} to keep the browser from continuing to process the touch event (this also prevents a mouse event from also being delivered). Then we get the context and pull the list of changed touch points out of the event's {{ domxref("TouchEvent.changedTouches") }} property.

After that, we iterate over all the {{ domxref("Touch") }} objects in the list, pushing them onto an array of active touch points and drawing the start point for the draw as a small rectangle; we're using a 4-pixel wide line, so we're drawing a 4-by-4 pixel square as the point for consistency.

Drawing as the touches move

Each time one or more fingers moves, a touchmove event is delivered, resulting in our handleMove() function being called. Its responsibility in this example is to update the cached touch information and to draw a line from the previous position to the current position of each touch.

function handleMove(evt) {
  evt.preventDefault();
  var el = document.getElementsByTagName("canvas")[0];
  var ctx = el.getContext("2d");
  var touches = evt.changedTouches;
  
  ctx.lineWidth = 4;
        
  for (var i=0; i<touches.length; i++) {
    var color = colorForTouch(touches[i]);
    var idx = ongoingTouchIndexById(touches[i].identifier);

    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(ongoingTouches[idx].pageX, ongoingTouches[idx].pageY);
    ctx.lineTo(touches[i].pageX, touches[i].pageY);
    ctx.closePath();
    ctx.stroke();
    ongoingTouches.splice(idx, 1, touches[i]);  // swap in the new touch record
  }
}

This iterates over the changed touches as well, but it looks in our cached touch information array for the previous information about each touch in order to determine the starting point for each touch's new line segment to be drawn. This is done by looking at each touch's {{ domxref("Touch.identifier") }} property. This property is a unique integer for each touch, and remains consistent for each event during the duration of each finger's contact with the surface.

This lets us get the coordinates of the previous position of each touch and use the appropriate context methods to draw a line segment joining the two positions together.

After drawing the line, we call Array.splice() to replace the previous information about the touch point with the current information in the ongoingTouches array.

Handling the end of a touch

When the user lifts a finger off the surface, a touchend event is sent. Similarly, if the finger drifts out of our canvas, we get a touchleave event. We handle both of these the same way: by calling the handleEnd() function below. Its job is to draw the last line segment for each touch that ended and remove the touch point from the ongoing touch list.

function handleEnd(evt) {
  evt.preventDefault();
  var el = document.getElementsByTagName("canvas")[0];
  var ctx = el.getContext("2d");
  var touches = evt.changedTouches;
  
  ctx.lineWidth = 4;
        
  for (var i=0; i<touches.length; i++) {
    var color = colorForTouch(touches[i]);
    var idx = ongoingTouchIndexById(touches[i].identifier);
    
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(ongoingTouches[i].pageX, ongoingTouches[i].pageY);
    ctx.lineTo(touches[i].pageX, touches[i].pageY);
    ongoingTouches.splice(i, 1);  // remove it; we're done
  }
}

This is very similar to the previous function; the only real difference is that when we call Array.splice(), we simply remove the old entry from the ongoing touch list, without adding in the updated information. The result is that we stop tracking that touch point.

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();
  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 the rest of the code make 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 will always be a value between 0 and one less than the number of active touches. Since it's highly improbable that anyone with more than 16 fingers will use this demo, we convert these directly into grayscale colors.

function colorForTouch(touch) {
  var id = touch.identifier;
  id = id.toString(16); // make it a hex digit
  return "#" + id + id + id;
}

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

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
}

{{ DOMLiveSample("touchevents.html") }}

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 over simplified 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 (event.type) {
    case "touchstart": type = "mousedown"; touch = event.changedTouches[[0];
    case "touchmove":  type = "mousemove"; touch = event.changedTouches[[0];
    case "touchend":   type = "mouseup"; touch = event.changedTouches[0];
  }
  newEvt.initMouseEvent(type, true, true, event.originalTarget.ownerDocument.defaultView, 0,
    touch.screenX, touch.screenY, touch.clientX, touch.clientY,
    evt.ctrlKey, evt.altKey, evt.shirtKey, evt.metaKey, 0, null);
  event.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, its not recommended to depend on any particular behavior in this case, but rather to depend on meta viewport to prevent zooming.

Browser compatibility

{{ CompatibilityTable() }}

Feature Chrome Firefox (Gecko) Internet Explorer Opera Safari
Basic support {{ CompatChrome("22.0") }} {{ CompatGeckoDesktop("18.0") }} {{ CompatNo() }} {{ CompatNo() }} {{ CompatNo() }}
Feature Android Chrome for Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile
Basic support {{ CompatVersionUnknown() }} {{ CompatUnknown() }} {{ CompatGeckoMobile("6.0") }} {{ CompatUnknown() }} {{ CompatUnknown() }} {{ CompatVersionUnknown() }}

Gecko notes

The dom.w3c_touch_events.enabled preference can be used to enable and disable support for standard touch events; by default, they're enabled.

{{ gecko_callout_heading("12.0") }}

Prior to Gecko 12.0 {{ geckoRelease("12.0") }}, Gecko did not support multi-touch; only one touch at a time was reported.

Note: Prior to Gecko 6.0 {{ geckoRelease("6.0") }}, Gecko offered a proprietary touch event API. That API is now deprecated; you should switch to this one."

Fuente de la revisión

<p>{{ DevDerbyPromoHeader("touch events") }}</p>
<p>In order to provide quality support for touch-based user interfaces, touch events offer the ability to interpret finger activity on touch screens or trackpads.</p>
<h2 id="Definitions">Definitions</h2>
<dl>
  <dt>
    Surface</dt>
  <dd>
    The touch-sensitive surface. This may be a screen or trackpad.</dd>
</dl>
<dl>
  <dt>
    <span style="font-weight: bold;">T</span>ouch point</dt>
  <dd>
    A point of contact with the surface. This may be a finger (or elbow, ear, nose, whatever, but probably a finger) or stylus.</dd>
</dl>
<h2 id="Interfaces">Interfaces</h2>
<dl>
  <dt>
    {{ domxref("TouchEvent") }}</dt>
  <dd>
    Represents an event that occurs when the state of touches on the surface changes.</dd>
  <dt>
    {{ domxref("Touch") }}</dt>
  <dd>
    Represents a single point of contact between the user and the touch surface.</dd>
  <dt>
    {{ domxref("TouchList") }}</dt>
  <dd>
    Represents a group of touches; this is used when the user has, for example, multiple fingers on the surface at the same time.</dd>
  <dt>
    {{ domxref("DocumentTouch") }}</dt>
  <dd>
    Contains convenience methods for creating {{ domxref("Touch") }}&nbsp;and {{ domxref("TouchList") }}&nbsp;objects.</dd>
</dl>
<h2 id="Example">Example</h2>
<p>This example tracks multiple touch points at a time, allowing the user to draw in a {{ HTMLElement("canvas") }} with more than one finger at a time. It will only work on a browser that supports touch events.</p>
<div class="note">
  <strong>Note:</strong> The text below uses the term "finger"&nbsp;when describing the contact with the surface, but it could, of course, also be a stylus or other contact method.</div>
<h3 id="Setting_up_the_event_handlers">Setting up the event handlers</h3>
<p>When the page loads, the <code>startup()</code>&nbsp;function shown below is called by our {{ HTMLElement("body") }}&nbsp;element's <code>onload</code> attribute.</p>
<pre class="brush: js">
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("touchleave", handleLeave, false);
  el.addEventListener("touchmove", handleMove, false);
}
</pre>
<p>This simply sets up all the event listeners for our {{ HTMLElement("canvas") }} element so we can handle the touch events as they occur.</p>
<h3 id="Tracking_new_touches">Tracking new touches</h3>
<p>When a <code>touchstart</code> event occurs, indicating that a new touch on the surface has occurred, the <code>handleStart()</code>&nbsp;function below is called.</p>
<pre class="brush: js">
function handleStart(evt) {
  evt.preventDefault();
  var el = document.getElementsByTagName("canvas")[0];
  var ctx = el.getContext("2d");
  var touches = evt.changedTouches;
        
  for (var i=0; i&lt;touches.length; i++) {
    ongoingTouches.push(touches[i]);
    var color = colorForTouch(touches[i]);
    ctx.fillStyle = color;
    ctx.fillRect(touches[i].pageX-2, touches[i].pageY-2, 4, 4);
  }
}
</pre>
<p>This calls {{ domxref("event.preventDefault()") }} to keep the browser from continuing to process the touch event (this also prevents a mouse event from also being delivered). Then we get the context and pull the list of changed touch points out of the event's {{ domxref("TouchEvent.changedTouches") }} property.</p>
<p>After that, we iterate over all the {{ domxref("Touch") }}&nbsp;objects in the list, pushing them onto an array of active touch points and drawing the start point for the draw as a small rectangle; we're using a 4-pixel wide line, so we're drawing a 4-by-4 pixel square as the point for consistency.</p>
<h3 id="Drawing_as_the_touches_move">Drawing as the touches move</h3>
<p>Each time one or more fingers moves, a <code>touchmove</code> event is delivered, resulting in our <code>handleMove()</code>&nbsp;function being called. Its responsibility in this example is to update the cached touch information and to draw a line from the previous position to the current position of each touch.</p>
<pre class="brush: js">
function handleMove(evt) {
  evt.preventDefault();
  var el = document.getElementsByTagName("canvas")[0];</pre>
<pre class="brush: js">
  var ctx = el.getContext("2d");
  var touches = evt.changedTouches;
  
  ctx.lineWidth = 4;
        
  for (var i=0; i&lt;touches.length; i++) {
    var color = colorForTouch(touches[i]);
    var idx = ongoingTouchIndexById(touches[i].identifier);

    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(ongoingTouches[idx].pageX, ongoingTouches[idx].pageY);
    ctx.lineTo(touches[i].pageX, touches[i].pageY);
    ctx.closePath();
    ctx.stroke();
    ongoingTouches.splice(idx, 1, touches[i]);  // swap in the new touch record
  }
}
</pre>
<p>This iterates over the changed touches as well, but it looks in our cached touch information array for the previous information about each touch in order to determine the starting point for each touch's new line segment to be drawn. This is done by looking at each touch's {{ domxref("Touch.identifier") }}&nbsp;property. This property is a unique integer for each touch, and remains consistent for each event during the duration of each finger's contact with the surface.</p>
<p>This lets us get the coordinates of the previous position of each touch and use the appropriate context methods to draw a line segment joining the two positions together.</p>
<p>After drawing the line, we call <a href="/en/JavaScript/Reference/Global_Objects/Array/splice" title="en/JavaScript/Reference/Global Objects/Array/splice"><code>Array.splice()</code></a>&nbsp;to replace the previous information about the touch point with the current information in the <code>ongoingTouches</code> array.</p>
<h3 id="Handling_the_end_of_a_touch">Handling the end of a touch</h3>
<p>When the user lifts a finger off the surface, a <code>touchend</code> event is sent. Similarly, if the finger drifts out of our canvas, we get a <code>touchleave</code> event. We handle both of these the same way:&nbsp;by calling the <code>handleEnd()</code>&nbsp;function below. Its job is to draw the last line segment for each touch that ended and remove the touch point from the ongoing touch list.</p>
<pre class="brush: js">
function handleEnd(evt) {
  evt.preventDefault();
  var el = document.getElementsByTagName("canvas")[0];</pre>
<pre class="brush: js">
  var ctx = el.getContext("2d");
  var touches = evt.changedTouches;
  
  ctx.lineWidth = 4;
        
  for (var i=0; i&lt;touches.length; i++) {
    var color = colorForTouch(touches[i]);
    var idx = ongoingTouchIndexById(touches[i].identifier);
    
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(ongoingTouches[i].pageX, ongoingTouches[i].pageY);
    ctx.lineTo(touches[i].pageX, touches[i].pageY);
    ongoingTouches.splice(i, 1);  // remove it; we're done
  }
}
</pre>
<p>This is very similar to the previous function; the only real difference is that when we call <a href="/en/JavaScript/Reference/Global_Objects/Array/splice" title="en/JavaScript/Reference/Global Objects/Array/splice"><code>Array.splice()</code></a>, we simply remove the old entry from the ongoing touch list, without adding in the updated information. The result is that we stop tracking that touch point.</p>
<h3 id="Handling_canceled_touches">Handling canceled touches</h3>
<p>If the user's finger wanders into browser UI, or the touch otherwise needs to be canceled, the <code>touchcancel</code> event is sent, and we call the <code>handleCancel()</code>&nbsp;function below.</p>
<pre class="brush: js">
function handleCancel(evt) {
  evt.preventDefault();
  var touches = evt.changedTouches;
  
  for (var i=0; i&lt;touches.length; i++) {
    ongoingTouches.splice(i, 1);  // remove it; we're done
  }
}
</pre>
<p>Since the idea is to immediately abort the touch, we simply remove it from the ongoing touch list without drawing a final line segment.</p>
<h3 id="Convenience_functions">Convenience functions</h3>
<p>This example uses two convenience functions that should be looked at briefly to help the rest of the code make more clear.</p>
<h4 id="Selecting_a_color_for_each_touch">Selecting a color for each touch</h4>
<p>In order to make each touch's drawing look different, the <code>colorForTouch()</code>&nbsp;function is used to pick a color based on the touch's unique identifier. This identifier will always be a value between 0 and one less than the number of active touches. Since it's highly improbable that anyone with more than 16 fingers will use this demo, we convert these directly into grayscale colors.</p>
<pre class="brush: js">
function colorForTouch(touch) {
  var id = touch.identifier;
  id = id.toString(16); // make it a hex digit
  return "#" + id + id + id;
}
</pre>
<p>The result from this function is a string that can be used when calling {{ HTMLElement("canvas") }}&nbsp;functions to set drawing colors. For example, for a {{ domxref("Touch.identifier") }} value of 10, the resulting string is "#aaa".</p>
<h4 id="Finding_an_ongoing_touch">Finding an ongoing touch</h4>
<p>The <code>ongoingTouchIndexById()</code>&nbsp;function below scans through the <code>ongoingTouches</code> array to find the touch matching the given identifier, then returns that touch's index into the array.</p>
<pre class="brush: js">
function ongoingTouchIndexById(idToFind) {
  for (var i=0; i&lt;ongoingTouches.length; i++) {
    var id = ongoingTouches[i].identifier;
    
    if (id == idToFind) {
      return i;
    }
  }
  return -1;    // not found
}
</pre>
<p>{{ DOMLiveSample("touchevents.html") }}</p>
<h2 id="Additional_tips">Additional tips</h2>
<p>This section provides additional tips on how to handle touch events in your web application.</p>
<h3 id="Handling_clicks">Handling clicks</h3>
<p>Since calling <code>preventDefault()</code> on a <code>touchstart</code> or the first <code>touchmove</code> event of a series prevents the corresponding mouse events from firing, it's common to call <code>preventDefault()</code> on <code>touchmove</code> rather than <code>touchstart</code>. 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 over simplified and may result in strange behavior. It is only intended as a guide).</p>
<pre class="brush: js">
function onTouch(evt) {
  evt.preventDefault();
  if (evt.touches.length &gt; 1 || (evt.type == "touchend" &amp;&amp; evt.touches.length &gt; 0))
    return;

  var newEvt = <a href="https://developer.mozilla.org/en/DOM/document.createEvent" rel="internal" title="en/DOM/document.createEvent">document.createEvent</a>("MouseEvents");
  var type = null;
  var touch = null;
  switch (event.type) {
    case "touchstart": type = "mousedown"; touch = event.changedTouches[[0];
    case "touchmove":  type = "mousemove"; touch = event.changedTouches[[0];
    case "touchend":   type = "mouseup"; touch = event.changedTouches[0];
  }
  newEvt.<strong>initMouseEvent</strong>(type, true, true, event.<code><a href="https://developer.mozilla.org/en/DOM/event.originalTarget" rel="custom">originalTarget</a>.ownerDocument.defaultView</code>, 0,
    touch.screenX, touch.screenY, touch.clientX, touch.clientY,
    evt.ctrlKey, evt.altKey, evt.shirtKey, evt.metaKey, 0, null);
  event.<code><a href="https://developer.mozilla.org/en/DOM/event.originalTarget" rel="custom">originalTarget</a></code>.<a href="https://developer.mozilla.org/en/DOM/element.dispatchEvent" rel="internal" title="en/DOM/element.dispatchEvent">dispatchEvent</a>(newEvt);
}
</pre>
<h3 id="Calling_preventDefault()_only_on_a_second_touch">Calling preventDefault() only on a second touch</h3>
<p>One technique for preventing things like <code>pinchZoom</code> on a page is to call <code>preventDefault()</code> 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, its not recommended to depend on any particular behavior in this case, but rather to depend on meta viewport to prevent zooming.</p>
<dl>
</dl>
<h2 id="Browser_compatibility">Browser compatibility</h2>
<p>{{ CompatibilityTable() }}</p>
<div id="compat-desktop">
  <table class="compat-table">
    <tbody>
      <tr>
        <th>Feature</th>
        <th>Chrome</th>
        <th>Firefox (Gecko)</th>
        <th>Internet Explorer</th>
        <th>Opera</th>
        <th>Safari</th>
      </tr>
      <tr>
        <td>Basic support</td>
        <td>{{ CompatChrome("22.0") }}</td>
        <td>{{ CompatGeckoDesktop("18.0") }}</td>
        <td>{{ CompatNo() }}</td>
        <td>{{ CompatNo() }}</td>
        <td>{{ CompatNo() }}</td>
      </tr>
    </tbody>
  </table>
</div>
<div id="compat-mobile">
  <table class="compat-table">
    <tbody>
      <tr>
        <th>Feature</th>
        <th>Android</th>
        <th>Chrome for Android</th>
        <th>Firefox Mobile (Gecko)</th>
        <th>IE Mobile</th>
        <th>Opera Mobile</th>
        <th>Safari Mobile</th>
      </tr>
      <tr>
        <td>Basic support</td>
        <td>{{ CompatVersionUnknown() }}</td>
        <td>{{ CompatUnknown() }}</td>
        <td>{{ CompatGeckoMobile("6.0") }}</td>
        <td>{{ CompatUnknown() }}</td>
        <td>{{ CompatUnknown() }}</td>
        <td>{{ CompatVersionUnknown() }}</td>
      </tr>
    </tbody>
  </table>
</div>
<h3 id="Gecko_notes">Gecko notes</h3>
<p>The <code>dom.w3c_touch_events.enabled</code> preference can be used to enable and disable support for standard touch events; by default, they're enabled.</p>
<div class="geckoVersionNote" style="undefined">
  <p>{{ gecko_callout_heading("12.0") }}</p>
  <p>Prior to Gecko 12.0 {{ geckoRelease("12.0") }}, Gecko did not support multi-touch; only one touch at a time was reported.</p>
</div>
<div class="note">
  <strong>Note: </strong>Prior to Gecko 6.0 {{ geckoRelease("6.0") }}, Gecko offered a <a href="/en/DOM/Touch_events_(Mozilla_experimental)" title="en/DOM/Touch events (Mozilla experimental)">proprietary touch event API</a>. That API is now deprecated; you should switch to this one."</div>
Revertir a esta revisión