События касания
Для обеспечения качественной поддержки сенсорных пользовательских интерфейсов, события касания предоставляют возможность интерпретировать активность пальцев (или стилуса) на сенсорных экранах или трекпадах.
Интерфейсы событий касания — это относительно низкоуровневые API, которые могут использоваться для поддержки специфичных для приложения многоточечных взаимодействий, таких как жесты с двумя пальцами. Многоточечное взаимодействие начинается, когда палец (или стилус) впервые касается сенсорной поверхности. Другие пальцы могут затем коснуться поверхности и, необязательно, перемещаться по сенсорной поверхности. Взаимодействие заканчивается, когда пальцы удаляются с поверхности. Во время этого взаимодействия приложение получает события касания на начальной, перемещающейся и конечной фазах.
События касания похожи на события мыши, за исключением того, что они поддерживают одновременные касания в разных местах на сенсорной поверхности. Интерфейс TouchEvent обобщает все активные точки касания. Интерфейс Touch, который представляет собой отдельную точку касания, включает информацию, такую как положение точки касания относительно области просмотра браузера.
Определения
- Поверхность
-
Чувствительная к касанию поверхность. Это может быть экран или трекпад.
- Точка касания
-
Точка контакта с поверхностью. Это может быть палец (или локоть, ухо, нос, что угодно, но обычно палец) или стилус.
Интерфейсы
TouchEvent-
Представляет событие, которое происходит при изменении состояния касаний на поверхности.
Touch-
Представляет собой отдельную точку контакта между пользователем и сенсорной поверхностью.
TouchList-
Представляет группу касаний; это используется, когда у пользователя, например, несколько пальцев на поверхности одновременно.
Пример
В этом примере отслеживаются несколько точек касания одновременно, что позволяет пользователю рисовать на <canvas> с помощью более чем одного пальца одновременно. Он будет работать только в браузере, который поддерживает события касания.
Примечание: В тексте ниже используется термин «палец» для описания контакта с поверхностью, но это, конечно, также может быть стилус или другой метод контакта.
Создание холста
<canvas id="canvas" width="600" height="600" style="border:solid black 1px;"> Your browser does not support canvas element. </canvas> <br /> Log: <pre id="log" style="border: 1px solid #ccc;"></pre>
#log {
height: 200px;
width: 600px;
overflow: scroll;
}
Настройка обработчиков событий
При загрузке страницы будет вызвана функция startup(), показанная ниже. Она устанавливает все обработчики событий для нашего элемента <canvas>, чтобы мы могли обрабатывать события касания по мере их возникновения.
function startup() {
const el = document.getElementById("canvas");
el.addEventListener("touchstart", handleStart);
el.addEventListener("touchend", handleEnd);
el.addEventListener("touchcancel", handleCancel);
el.addEventListener("touchmove", handleMove);
log("Initialized.");
}
document.addEventListener("DOMContentLoaded", startup);
Отслеживание новых касаний
Мы будем отслеживать касания в процессе.
const ongoingTouches = [];
При возникновении события touchstart, указывающего на то, что произошло новое касание поверхности, вызывается функция handleStart() ниже.
function handleStart(evt) {
evt.preventDefault();
log("touchstart.");
const el = document.getElementById("canvas");
const ctx = el.getContext("2d");
const touches = evt.changedTouches;
for (let i = 0; i < touches.length; i++) {
log(`touchstart: ${i}.`);
ongoingTouches.push(copyTouch(touches[i]));
const color = colorForTouch(touches[i]);
log(`color of touch with id ${touches[i].identifier} = ${color}`);
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();
}
}
Это вызывает event.preventDefault(), чтобы предотвратить дальнейшую обработку браузером события касания (это также предотвращает доставку события мыши). Затем мы получаем контекст и извлекаем список измененных точек касания из свойства события TouchEvent.changedTouches.
После этого мы итерируем по всем объектам Touch в списке, помещая их в массив активных точек касания и рисуя начальную точку для рисования в виде маленького круга; мы используем линию шириной 4 пикселя, поэтому круг радиусом 4 пикселя будет хорошо отображаться.
Рисование при перемещении касаний
Каждый раз, когда один или несколько пальцев перемещаются, доставляется событие touchmove, что приводит к вызову нашей функции handleMove(). В этом примере её задача — обновить кэшированную информацию о касаниях и нарисовать линию от предыдущего положения к текущему положению каждого касания.
function handleMove(evt) {
evt.preventDefault();
const el = document.getElementById("canvas");
const ctx = el.getContext("2d");
const touches = evt.changedTouches;
for (let i = 0; i < touches.length; i++) {
const color = colorForTouch(touches[i]);
const 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
} else {
log("can't figure out which touch to continue");
}
}
}
Это итерирует по изменённым касаниям, но ищет в нашем кэшированном массиве информации о касаниях предыдущую информацию о каждом касании, чтобы определить начальную точку для каждого сегмента линии, подлежащего рисованию. Это делается путем обращения к свойству Touch.identifier каждого касания. Это свойство — уникальное целое число для каждого касания и остается неизменным для каждого события в течение всего времени контакта каждого пальца с поверхностью.
Это позволяет получить координаты предыдущего положения каждого касания и использовать соответствующие методы контекста для рисования сегмента линии, соединяющего два положения.
После рисования линии мы вызываем Array.splice(), чтобы заменить предыдущую информацию о точке касания текущей информацией в массиве ongoingTouches.
Обработка окончания касания
Когда пользователь поднимает палец с поверхности, отправляется событие touchend. Мы обрабатываем это, вызвав функцию handleEnd() ниже. Её задача — нарисовать последний сегмент линии для каждого завершенного касания и удалить точку касания из списка текущих касаний.
function handleEnd(evt) {
evt.preventDefault();
log("touchend");
const el = document.getElementById("canvas");
const ctx = el.getContext("2d");
const touches = evt.changedTouches;
for (let i = 0; i < touches.length; i++) {
const color = colorForTouch(touches[i]);
let 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() мы удаляем старую запись из текущего списка касаний, не добавляя обновлённую информацию. Результатом является прекращение отслеживания этой точки касания.
Обработка отменённых касаний
Если палец пользователя заходит на область пользовательского интерфейса браузера или касание по какой-либо причине должно быть отменено, отправляется событие touchcancel, и мы вызываем функцию handleCancel() ниже.
function handleCancel(evt) {
evt.preventDefault();
log("touchcancel.");
const touches = evt.changedTouches;
for (let i = 0; i < touches.length; i++) {
let idx = ongoingTouchIndexById(touches[i].identifier);
ongoingTouches.splice(idx, 1); // remove it; we're done
}
}
Поскольку идея состоит в немедленном прерывании касания, мы удаляем его из текущего списка касаний, не рисуя конечный сегмент линии.
Вспомогательные функции
В этом примере используются две вспомогательные функции, которые следует рассмотреть кратко, чтобы сделать остальной код более понятным.
Выбор цвета для каждого касания
Чтобы каждое касание рисовать в разных цветах, функция colorForTouch() используется для выбора цвета на основе уникального идентификатора касания. Этот идентификатор — непрозрачное число, но мы, по крайней мере, можем положиться на то, что он различается между текущими активными касаниями.
function colorForTouch(touch) {
let r = touch.identifier % 16;
let g = Math.floor(touch.identifier / 3) % 16;
let 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
const color = `#${r}${g}${b}`;
return color;
}
Результат этой функции — строка, которая может использоваться при вызове функций <canvas> для установки цветов рисования. Например, для значения Touch.identifier 10, результирующая строка — "#a31".
Копирование объекта касания
В некоторых браузерах (например, мобильный Safari) объекты касания повторно используются между событиями, поэтому лучше копировать свойства, которые вас интересуют, а не ссылаться на весь объект.
function copyTouch({ identifier, pageX, pageY }) {
return { identifier, pageX, pageY };
}
Поиск текущего касания
Функция ongoingTouchIndexById() ниже сканирует массив ongoingTouches , чтобы найти касание, соответствующее заданному идентификатору, а затем возвращает индекс этого касания в массиве.
function ongoingTouchIndexById(idToFind) {
for (let i = 0; i < ongoingTouches.length; i++) {
const id = ongoingTouches[i].identifier;
if (id === idToFind) {
return i;
}
}
return -1; // not found
}
Отображение происходящего
function log(msg) {
const container = document.getElementById("log");
container.textContent = `${msg} \n${container.textContent}`;
}
Результат
Вы можете проверить этот пример на мобильных устройствах, коснувшись области ниже.
Примечание: В более общем смысле, пример будет работать на платформах, которые предоставляют события касания. Вы можете проверить это на настольных платформах, которые могут имитировать такие события:
- В Firefox включите «имитацию касаний» в режиме отзывчивого дизайна (возможно, потребуется перезагрузить страницу).
- В Chrome используйте режим эмуляции устройств и установите тип устройства на тот, который отправляет события касания.
Дополнительные советы
Этот раздел содержит дополнительные советы по обработке событий касания в вашем веб-приложении.
Обработка кликов
Поскольку вызов preventDefault() для события touchstart или первого события touchmove в серии предотвращает запуск соответствующих событий мыши, обычно вызывают preventDefault() на touchmove вместо touchstart. Таким образом, события мыши могут по-прежнему срабатывать, и такие вещи, как ссылки, продолжат работать. В качестве альтернативы, некоторые фреймворки переопределяют события касания в события мыши для той же цели. (Этот пример упрощен и может привести к странному поведению. Он предназначен только для справочных целей.)
function onTouch(evt) {
evt.preventDefault();
if (
evt.touches.length > 1 ||
(evt.type === "touchend" && evt.touches.length > 0)
)
return;
const newEvt = document.createEvent("MouseEvents");
let type = null;
let 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);
}
Вызов preventDefault() только для второго касания
Один из способов предотвращения таких ситуаций, как pinchZoom, на странице — вызов preventDefault() при втором касании в серии. Это поведение не определено в спецификации событий касания и приводит к различным результатам в разных браузерах (например, iOS предотвратит масштабирование, но всё ещё позволит панорамирование двумя пальцами; Android позволит масштабирование, но не панорамирование; Opera и Firefox в настоящее время предотвращают всё панорамирование и масштабирование). В настоящее время не рекомендуется полагаться на какое-либо конкретное поведение в этом случае, а вместо этого полагаться на мета-viewport для предотвращения масштабирования.
Спецификации
| Спецификация |
|---|
| Touch Events |
Совместимость с браузерами
| Настольные | Мобильные | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Opera | Safari | Chrome Android | Firefox для Android | Opera Android | Safari на IOS | Samsung Internet | WebView Android | |
Touch_events |
18 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 18 | 6 | 14 | 2 | 1.0 | 4.4 |
item |
18 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 18 | 6 | 14 | 2 | 1.0 | 4.4 |
length |
18 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 18 | 6 | 14 | 2 | 1.0 | 4.4 |
| Настольные | Мобильные | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Opera | Safari | Chrome Android | Firefox для Android | Opera Android | Safari на IOS | Samsung Internet | WebView Android | |
TouchEvent |
48Chrome поддерживает только следующие свойстваtouchEventInit: touches, targetTouches, changedTouches. |
79Edge поддерживает только следующие свойстваtouchEventInit: touches, targetTouches, changedTouches. |
Нет | 35Opera поддерживает только следующие свойстваtouchEventInit: touches, targetTouches, changedTouches. |
Нет | 48Chrome Android поддерживает только следующие свойстваtouchEventInit: touches, targetTouches, changedTouches. |
46 | 35Opera Android поддерживает только следующие свойстваtouchEventInit: touches, targetTouches, changedTouches. |
3.2 | 5.0Samsung Internet поддерживает только следующие свойстваtouchEventInit: touches, targetTouches, changedTouches. |
48WebView Android поддерживает только следующие свойстваtouchEventInit: touches, targetTouches, changedTouches. |
Touch_events |
22 | 79 | 5218–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 25 | 6 | 14 | 3.2 | 1.5 | 4.4 |
altKey |
22 | 79 | 5218–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 25 | 6 | 14 | 3.2 | 1.5 | 4.4 |
changedTouches |
22 | 79 | 5218–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 25 | 6 | 14 | 3.2 | 1.5 | 4.4 |
ctrlKey |
22 | 79 | 5218–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 25 | 6 | 14 | 3.2 | 1.5 | 4.4 |
metaKey |
22 | 79 | 5218–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 25 | 6 | 14 | 3.2 | 1.5 | 4.4 |
shiftKey |
22 | 79 | 5218–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 25 | 6 | 14 | 3.2 | 1.5 | 4.4 |
targetTouches |
22 | 79 | 5218–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 25 | 6 | 14 | 3.2 | 1.5 | 4.4 |
touches |
22 | 79 | 5218–24Удалено в bug 888304 из-за проблем совместимости с веб-сайтами. |
15 | Нет | 25 | 6 | 14 | 3.2 | 1.5 | 4.4 |
| Настольный | Мобильный | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Opera | Safari | Chrome Android | Firefox for Android | Opera Android | Safari on IOS | Samsung Internet | WebView Android | |
Touch |
48 | 79 | 52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода. |
35 | Нет | 48 | 6 | 35 | ≤3 | 5.0 | 48 |
Touch_events |
22 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
15 | Нет | 25 | 6 | 14 | ≤3 | 1.5 | 4.4 |
altitudeAngle |
Нет | Нет | Нет | Нет | Нет | Нет | Нет | Нет | 10 | Нет | Нет |
azimuthAngle |
Нет | Нет | Нет | Нет | Нет | Нет | Нет | Нет | 10 | Нет | Нет |
clientX |
22 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
15 | Нет | 25 | 6 | 14 | 10 | 1.5 | 4.4 |
clientY |
22 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
15 | Нет | 25 | 6 | 14 | 10 | 1.5 | 4.4 |
force |
3822–47 | ≤79 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
2515–34 | Нет | 3825–47 | 6 | 2514–34 | 10 | 3.01.5–5.0 | 384.4–47 |
identifier |
22 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
15 | Нет | 25 | 6 | 14 | 10 | 1.5 | 4.4 |
pageX |
22 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
15 | Нет | 25 | 6 | 14 | 10 | 1.5 | 4.4 |
pageY |
22 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
15 | Нет | 25 | 6 | 14 | 10 | 1.5 | 4.4 |
radiusX |
3822–47 | ≤79 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
2515–34 | Нет | 3825–47 | 6 | 2514–34 | 10 | 3.01.5–5.0 | 384.4–47 |
radiusY |
3822–47 | ≤79 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
2515–34 | Нет | 3825–47 | 6 | 2514–34 | 10 | 3.01.5–5.0 | 384.4–47 |
rotationAngle |
4322–47 | ≤79 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
3015–34 | Нет | 4325–47 | 6 | 3014–34 | 10 | 4.01.5–5.0 | 434.4–47 |
screenX |
22 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
15 | Нет | 25 | 6 | 14 | 10 | 1.5 | 4.4 |
screenY |
22 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
15 | Нет | 25 | 6 | 14 | 10 | 1.5 | 4.4 |
target |
22 | ≤18 |
52Этот интерфейс доступен только при обнаружении сенсорного устройства ввода.18–24Удалено в bug 888304 из-за проблем совместимости с веб-стандартами. |
15 | Нет | 25 | 6 | 14 | 10 | 1.5 | 4.4 |
touchType |
Нет | Нет | Нет | Нет | Нет | Нет | Нет | Нет | 10 | Нет | Нет |
api.Touch
Таблицы BCD загружаются только в браузере
api.TouchEvent
Таблицы BCD загружаются только в браузере
api.TouchList
Таблицы BCD загружаются только в браузере
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Touch_events