1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
|
---
title: События касаний (тач-события)
slug: Web/API/Touch_events
translation_of: Web/API/Touch_events
---
<div>{{DefaultAPISidebar("Touch Events")}}</div>
<p>Чтобы предоставить качественную поддержку пользовательского интерфейса, связанного с касаниями, тач-события предлагают возможность интерпретировать действия пальца (или стилуса) на сенсорных экранах или трекпадах.</p>
<p>Интерфейсы событий касания - это относительно низкоуровневые API, которые можно использовать для поддержки приложений со специфическими мультитач-взаимодействиями, например жестом двумя пальцами. Мультитач взаимодействие запускается, когда палец (или стилус) впервые касается контактной поверхности. Другие пальцы могут следом коснуться поверхности и, если нужно, перемещаться по ней. Взаимодействие считается завершённым, когда пальцы с поверхности убираются. Во время взаимодействия, события касания срабатывают на начальном этапе (первое прикосновение), этапе перемещения по поверхности, и завершающем этапе (когда пальцы убираются с поверхности)..</p>
<p>События касаний подобны событиям мыши за исключением того, что они поддерживают несколько одновременных касаний в разных местах сенсорной поверхности. Интерфейс {{domxref("TouchEvent")}} содержит все активные в данный момент точки прикосновения. Интерфейс {{domxref("Touch")}}, который представляет одну точку касания, включает такую информацию, как позиция точки касания относительно области видимости браузера.</p>
<h2 id="Определения">Определения</h2>
<dl>
<dt>Поверхность (Surface)</dt>
<dd>Чувствительная к касаниям поверхность. Это может быть экран или трекпад.</dd>
</dl>
<dl>
<dt>Точка касания (Touch point)</dt>
<dd>Точка контакта с поверхностью. Это может быть палец (или локоть, ухо, нос, что угодно, но обычно это палец) или стилус. </dd>
</dl>
<h2 id="Интерфейсы">Интерфейсы</h2>
<dl>
<dt>{{domxref("TouchEvent")}}</dt>
<dd>Представляет событие, происходящее при изменении состояния касания поверхности.</dd>
<dt>{{domxref("Touch")}}</dt>
<dd>Представляет одну точку контакта пользователя с сенсорной поверхностью.</dd>
<dt>{{domxref("TouchList")}}</dt>
<dd>Представляет группу касаний; это используется, если пользователь, например, касается поверхности несколькими пальцами одновременно.</dd>
</dl>
<h2 id="Пример">Пример</h2>
<p>В этом примере отслеживаются несколько касаний одновременно, позволяя пользователю рисовать в {{HTMLElement("canvas")}} несколькими пальцами одновременно. Это будет работать лишь в браузере, который поддерживает touch-события.</p>
<div class="note"><strong>Примечание:</strong> В тексте ниже для описания контакта с поверхностью используется понятие "палец", но это также может быть стилус или другой способ контакта.</div>
<h3 id="Создание_canvas">Создание canvas</h3>
<pre class="brush: html notranslate"><canvas id="canvas" width="600" height="600" style="border:solid black 1px;">
Ваш браузер не поддерживает элемент canvas.
</canvas>
<br>
Log: <pre id="log" style="border: 1px solid #ccc;"></pre></pre>
<h3 id="Добавление_обработчиков_событий">Добавление обработчиков событий</h3>
<p>После загрузки страницы будет вызвана функция <code>startup()</code>, показанная ниже</p>
<pre class="brush: js notranslate">function startup() {
var el = document.getElementById("canvas");
el.addEventListener("touchstart", handleStart, false);
el.addEventListener("touchend", handleEnd, false);
el.addEventListener("touchcancel", handleCancel, false);
el.addEventListener("touchmove", handleMove, false);
}</pre>
<p>document.addEventListener("DOMContentLoaded", startup);</p>
<p>Данная функция просто добавляет обработчики событий для элемента {{HTMLElement("canvas")}}, чтобы мы могли обрабатывать события касания по мере их возникновения</p>
<h4 id="Отслеживание_новых_касаний">Отслеживание новых касаний</h4>
<p>Рассмотрим касания в действии.</p>
<pre class="brush: js notranslate">var ongoingTouches = [];
</pre>
<p>Когда возникает событие {{event("touchstart")}}, свидетельствующее о новом касании к поверхности, вызывается приведённая ниже функция <code>handleStart()</code>.</p>
<pre class="brush: js notranslate">function handleStart(evt) {
evt.preventDefault();
console.log("touchstart.");
var el = document.getElementById("canvas");
var ctx = el.getContext("2d");
var touches = evt.changedTouches;
for (var i = 0; i < touches.length; i++) {
console.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();
console.log("touchstart:" + i + ".");
}
}
</pre>
<p>Она вызывает {{domxref("event.preventDefault()")}} для того, чтобы предотвратить обработку браузером события касания (это также предотвращает обработку события мыши). Затем мы получаем контекст и извлекаем список изменённых точек касаний из свойства {{domxref("TouchEvent.changedTouches")}} данного события.</p>
<p>После этого мы перебираем все объекты {{domxref("Touch")}} в списке, помещая их в массив активных точек касания и рисуя начальную точку в виде маленького кружка; мы используем линию толщиной 4 пикселя, поэтому получим аккуратный круг радиусом 4 пикселя.</p>
<h4 id="Рисование_движением">Рисование движением</h4>
<p>Каждый раз, когда двигается один или несколько пальцев, срабатывает событие {{event("touchmove")}}, в результате чего вызывается наша функция <code>handleMove()</code>.В этом примере данная функция ответственна за обновление данных о касании и рисование линии от предыдущей до текущей точки касания.</p>
<pre class="brush: js notranslate">function handleMove(evt) {
evt.preventDefault();
var el = document.getElementById("canvas");
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) {
console.log("continuing touch "+idx);
ctx.beginPath();
console.log("ctx.moveTo(" + ongoingTouches[idx].pageX + ", " + ongoingTouches[idx].pageY + ");");
ctx.moveTo(ongoingTouches[idx].pageX, ongoingTouches[idx].pageY);
console.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
console.log(".");
} else {
console.log("can't figure out which touch to continue");
}
}
}
</pre>
<p>Функция также перебирает изменённые касания, ищет в нашем массиве данных о касаниях предыдущие данные о каждом касании для определения начальной точки каждого отрезка линии, который должен быть нарисован с помощью касания. Это реализовано путём отслеживания свойства {{domxref("Touch.identifier")}} каждого касания. Это свойство является уникальным числом для каждого касания, и остаётся неизменным на протяжении всего времени контакта пальца с экраном.</p>
<p>Это позволяет нам получать координаты предыдущей позиции каждого касания и использовать подходящий метод "canvas-контекста" для рисования отрезка линии, соединяющего начало и конец.</p>
<p>После рисования линии, мы вызываем <a href="/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/splice"><code>Array.splice()</code></a>, чтобы в массиве <code>ongoingTouches</code> заменить предыдущие данные о точке касания на текущие.</p>
<h4 id="Обработка_завершения_касания">Обработка завершения касания</h4>
<p>Когда пользователь убирает палец с сенсорной поверхности, срабатывает событие {{domxref("Element/touchend_event", "touchend")}}. Мы обрабатываем его, вызывая функцию <code>handleEnd()</code>, которая представлена ниже. Её задача - рисовать последний отрезок линии для каждого касания, которое завершилось, и удалять точку касания из текущего списка касаний.</p>
<pre class="brush: js notranslate">function handleEnd(evt) {
evt.preventDefault();
log("touchend");
var el = document.getElementById("canvas");
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 {
console.log("can't figure out which touch to end");
}
}
}
</pre>
<p>Она очень похожа на предыдущую функцию; единственное отличие заключается в том, что,что теперь мы рисуем маленький квадрат, чтобы обозначить конец, и в том, что когда мы мы вызываем <code><a href="/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/splice">Array.splice()</a></code>, мы просто удаляем старую запись из текущего списка касаний без добавления обновлённой информации. В результате мы прекращаем отслеживать эту точку касания.</p>
<h4 id="Обработка_отменённых_касаний">Обработка отменённых касаний</h4>
<p>Если палец пользователя перемещается в интерфейс браузера или прикосновение должно быть отменено, отправляется событие {{domxref("Element/touchcancel_event", "touchcancel")}}, и мы вызываем функцию <code>handleCancel()</code>, приведённую ниже.</p>
<pre class="brush: js notranslate">function handleCancel(evt) {
evt.preventDefault();
console.log("touchcancel.");
var touches = evt.changedTouches;
for (var i = 0; i < touches.length; i++) {
var idx = ongoingTouchIndexById(touches[i].identifier);
ongoingTouches.splice(idx, 1); // remove it; we're done
}
}
</pre>
<p>Поскольку идея состоит в том, чтобы немедленно прервать касание, мы просто удаляем его из текущего списка касаний без рисования завершающего отрезка линии.</p>
<h3 id="Удобные_функции">Удобные функции</h3>
<p>Этот пример использует две удобные функции, которые следует кратко рассмотреть, чтобы сделать остальную часть кода более понятной.</p>
<h4 id="Выбор_цвета_для_каждого_касания">Выбор цвета для каждого касания</h4>
<p>Чтобы рисунок каждого касания выглядел по-разному, используется функция <code>colorForTouch()</code>, в которой цвета выбираются на основе уникального идентификатора касаний. Этот идентификатор является скрытым числом, но мы, по крайней мере, можем полагаться на то, что у каждого активного на данный момент касания он уникальный.</p>
<pre class="brush: js notranslate">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;
console.log("color for touch with identifier " + touch.identifier + " = " + color);
return color;
}
</pre>
<p>Результатом этой функции является строка, которую можно использовать при вызове {{HTMLElement("canvas")}}-функций для задания цвета рисования. Например, для значения {{domxref("Touch.identifier")}}, равного 10, результатом будет "#a31".</p>
<h4 id="Копирование_объекта_касания">Копирование объекта касания</h4>
<p>Некоторые браузеры (например, мобильный Safari) повторно используют объекты касания в разных событиях, поэтому лучше копировать только важные свойства, а не ссылаться на весь объект.</p>
<pre class="brush: js notranslate">function copyTouch({ identifier, pageX, pageY }) {
return { identifier, pageX, pageY };
}</pre>
<h4 id="Поиск_текущего_касания">Поиск текущего касания</h4>
<p>Приведённая ниже функция <code>ongoingTouchIndexById()</code> сканирует весь массив <code>ongoingTouches</code>, чтобы найти касание, соответствующее данному идентификатору, после чего возвращает в массив индекс этого касания.</p>
<pre class="brush: js notranslate">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
}
</pre>
<h4 id="Отображение_происходящего">Отображение происходящего</h4>
<pre class="brush: js notranslate">function log(msg) {
var p = document.getElementById('log');
p.innerHTML = msg + "\n" + p.innerHTML;
}</pre>
<p>Если ваш браузер поддерживает это, вы можете {{LiveSampleLink('Example', 'посмотреть живой пример')}}.</p>
<p><a href="http://jsfiddle.net/Darbicus/z3Xdx/10/">Пример sFiddle</a></p>
<h2 id="Дополнительные_советы">Дополнительные советы</h2>
<p>Этот раздел даёт дополнительные советы о том, как обрабатывать события касаний в ваших веб-приложениях.</p>
<h3 id="Обработка_кликов">Обработка кликов</h3>
<p>Поскольку вызов <code>preventDefault()</code> для события {{event("touchstart")}} или первого события из серии событий {{domxref("Element/touchmove_event", "touchmove")}} предотвращает запуск соответствующих событий мыши, более распространена практика вызова <code>preventDefault()</code> именно для события <code>touchmove</code>, а не <code>touchstart</code>. Таким образом, события мыши всё ещё будут вызываться, а такие элементы, как ссылки, будут продолжать работать. В качестве альтернативы, в некоторых фреймворках для этой же цели события касаний дублируются событиями мыши.</p>
<p>Данный пример очень упрощён и может привести к странному поведению. Он уместен исключительно как учебный пример.</p>
<pre class="brush: js notranslate">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);
}
</pre>
<h3 id="Вызов_preventDefault_только_при_втором_касании">Вызов preventDefault() только при втором касании</h3>
<p>Один из способов запретить использовать на странице <code>pinchZoom</code> (зум с помощью щипка), – вызвать <code>preventDefault()</code> для второго касания, когда одно касание уже активно. Такое поведение плохо прописано в спецификации событий касаний и приводит к разному поведению в разных браузерах. Например, iOS предотвратит зум, но всё ещё будет позволять перетаскивание (panning) двумя пальцами; в Android, наоборот, можно будет осуществлять перетаскивание (panning), но не зум; Opera и Firefox на данный момент предотвращают и перетаскивание (panning) и зум. На данный момент для запрета использования зума рекомендуется полагаться не на какое-то конкретное поведение, а на meta-данные для "viewport".</p>
<h2 id="Спецификация">Спецификация</h2>
<table class="standard-table">
<tbody>
<tr>
<th scope="col">Specification</th>
<th scope="col">Status</th>
<th scope="col">Comment</th>
</tr>
<tr>
<td>{{SpecName('Touch Events 2', '#touch-interface', 'Touch')}}</td>
<td>{{Spec2('Touch Events 2')}}</td>
<td>Добавлены свойства <code>radiusX</code>, <code>radiusY</code>, <code>rotationAngle</code>, <code>force</code></td>
</tr>
<tr>
<td>{{SpecName('Touch Events', '#touch-interface', 'Touch')}}</td>
<td>{{Spec2('Touch Events')}}</td>
<td>Начальное определение.</td>
</tr>
</tbody>
</table>
<h2 id="Совместимость_с_браузером">Совместимость с браузером</h2>
<h3 id="Touch">Touch</h3>
<p>События касаний обычно доступны на устройствах с сенсорными экранами, но многие браузеры делают API событий касаний недоступными на всех компьютерах, даже имеющих сенсорный экран.</p>
<p>Причина этого заключается в том, что некоторые веб-сайты используют поддержку данного API в качестве показателя того, что браузер запущен на мобильном устройстве. Если API событий касания доступен, значит эти сайты будут предполагать работу с мобильного устройства и предоставлять соответствующее содержимое, оптимизированное для мобильных устройств. Это может существенно усложнить работу для пользователей десктопов с сенсорными экранами.</p>
<p>Для поддержки и касаний и мыши на всех типах устройств, используйте вместо этого <a href="/en-US/docs/Web/API/Pointer_events">события указателя</a></p>
<p>{{Compat("api.Touch")}}</p>
<h3 id="Firefox_события_касаний_и_многопроцессность_e10s">Firefox, события касаний и многопроцессность (e10s)</h3>
<p>В Firefox события касания отключены, когда отключён e10s (электролиз; <a href="/en-US/docs/Mozilla/Firefox/Multiprocess_Firefox">многопроцессорный Firefox</a>). e10s включён по умолчанию в Firefox, но может в конечном итоге отключаться в определённых ситуациях, например, когда установлены определённые инструменты специальных возможностей или надстройки Firefox, для работы которых требуется отключение e10s. Это означает, что даже на настольном компьютере / ноутбуке с сенсорным экраном сенсорные события не будут включены.</p>
<p>Вы можете проверить, отключён ли e10s, перейдя в <code>about:support</code> и посмотрев на запись «Многопроцессорная Windows» в разделе «Основы приложения». 1/1 означает, что он включён, 0/1 означает отключён.</p>
<p>Если вы хотите принудительно включить e10s - чтобы явно повторно включить поддержку сенсорных событий - вам нужно перейти к <code>about:config</code> и создать новое логическое предпочтение <code>browser.tabs.remote.force-enable</code>. Установите значение <code>true</code>, перезапустите браузер, и e10s будет включён независимо от любых других настроек.</p>
|