Вступление
Недавно я писал компонент для React минималистичной галереи картинок. Чтобы мой компонент обрабатывал всеми привычные touch события (такие как свайп влево/вправо, для переключение картинок, или свайп вверх, чтобы закрыть галерею, зум и т.п.) мне пришлось разобраться как работают события прикосновения в javascript. Здесь я поделюсь своим опытом и постараюсь максимально подробно рассказать все основные фитчи которые есть в вашем javascript для обработки прикосновение к дисплею.
Еще немного отступления
Вообще в английской терминологии часто используют следующие термины:
Tap (тап = нажатие) - вы прикасаетесь к экрану одним пальцем и сразу же убираете его, не двигая по дисплею, тем самым имитируя клик мыши
Swipe (свайп = смахивание) - вы как бы чиркаете по вашему экрану, то есть прикасаетесь одним пальцем, быстро проводите по нему и сразу же убираете палец. Как правило здесь выделяют четыре направления: вверх, вниз, вправо, влево (например свайпни вправо), чаще всего нужно для имитации нажатия стрелочки на вашей клавиатуре.
Pich-to-zoom (пинч зум или зум = ущипнуть чтобы увеличить) - вы прикасаетесь двумя пальцами и разводите из в противоположные стороны к друг другу или от друг друга, чаще всего нужно для имитации прокрутки колеса мыши.
Также есть и другие менее популярные термины (такие как drag и т.п.), но не суть в них, javascript все равно не мыслит такими категориями и для нашего браузера все завязано на всего лишь четырёх событиях, с помощью которых можно реализовать все выше перечисленные действия и даже больше.
События прикосновения
В javascript существует 4 события которые вызываются при вашем прикосновении. Сами события помечаются как touch, именно как касание, а не finger например, потому что предполагается, что вы касаетесь, но чем именно неважно, например локтем или носом. То есть разработчики изначально разрабатывали интерфейс для более широких возможностей, чем только пальцы.
el.addEventListener('touchstart', () => { console.log('start') }); // el.ontouchstart = () => { console.log('start') };
el.addEventListener('touchend', () => { console.log('end') }); // el.ontouchstart = () => { console.log('start') };
el.addEventListener('touchmove', () => { console.log('move') }); // el.ontouchstart = () => { console.log('start') };
el.addEventListener('touchcancel', () => { console.log('cancel') }); // el.ontouchstart = () => { console.log('start') };
Как вы можете видеть выше эти события touchstart
, touchend
, touchcancel
, touchmove
и все они возвращают объект с интерфейсом TouchEvent
.
touchstart
- вызывается когда вы прикасаетесь к экрану, причем каждое касание вызывает это событие заново. Например, вы прикоснетесь одним пальцем, затем, не убирая первый палец, прикоснетесь другим, вызовутся два раза события. И даже если вы одновременно нажмете тремя пальцами, то вызовется три раза событиеtouchend
- работает по точно такому же принципу как и `touchstart`, но когда вы убираете палец с экрана.touchmove
- вызывается при любом движение любого пальца, например вы прикоснётесь к экрану и уберете его сразу, то данное событие не сработает, но если прикоснуться и провести куда-то, то оно сработает несколько раз в зависимости от того как долго вы будете двигать пальцем. Также если вы касаетесь тремя пальцами и двигаете только одним из трех, событие также срабатывает.touchcancel
- данное событие срабатывает, если по каким-то причинам пришлось прервать ваше касание, например ваш палец сдвинулся за рамки окна приложения или вы касаетесь больше пальцами чем может поддерживать ваш телефон. Мне лично это событие ни разу не пригождалось, но кому-то оно может быть полезным.
Важная заметка
el.addEventListener('click', () => { console.log('click') }); // сработает
el.addEventListener('contextmenu', () => { console.log('contextmenu') });
el.addEventListener('dblclick', () => { console.log('dblclick') });
el.addEventListener('mousedown', () => { console.log('mousedown') }); // сработает
el.addEventListener('mouseenter', () => { console.log('mouseenter') }); // сработает
el.addEventListener('mouseleave', () => { console.log('mouseleave') });
el.addEventListener('mousemove', () => { console.log('mousemove') }); // сработает
el.addEventListener('mouseout', () => { console.log('mouseout') });
el.addEventListener('mouseover', () => { console.log('mouseover') }); // сработает
el.addEventListener('mouseup', () => { console.log('mouseup') }); // сработает
el.addEventListener('wheel', () => { console.log('wheel') });
Если вы привяжите к вашему элементу все события мыши, как это сделано выше, и попробуете на вашем телефоне тапнуть по нему, то есть нажать на элемент и не двигая палец убрать, то вы увидите, что все события сработают кроме, dblclick
, mouseleave
, mouseout
Меня это и сейчас вводит в конфуз, почему tap обрабатывается как клик мыши, но вы сами можете проверить вставив код выше (по крайней мере на Chrome). Если же вы хотите, чтобы события мыши срабатывали только от мыши, то вот вам вариант как это можно обойти.
const isTouch = () => 'ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0
el.addEventListener('mousedown', (e) => {
if (isTouch()) return // если ваш экран поддерживает touch то взвращаем функцию
console.log('click')
});
То есть мы создаем функцию isTouch
, которая определяет - поддерживает ли ваше устройство touch события, и если да, то мы возвращаем функцию.
Точки прикосновения
Как уже было сказано все четыре события возвращают TouchEvent
Объект который возвращает содержит три основные свойства, про которые мы будем дальше говорить. Эти свойства changedTouches
, touches
и targetTouches
. Также еще на сайте firefox описано два свойства это rotation
и scale
, которые показывают поворот и увеличение, но у меня они не работают в chrome и я смотрел у разных людей, у них также не возвращаются данные свойства. Напишите, если у кого-то эти свойства есть в комментариях.
Чем отличаются .changedTouches, .touches и .targetTouches
Давайте рассмотрим картинку слева. Допустим у нас есть есть элемент, на который завязано события прикосновения touchmove
, он обозначен серым квадратом. Далее мы прикасаемся тремя пальцами как показано на рисунке синими точками и двигаем этими пальцами вправо. Что же будет у нас в свойствах .changedTouches
, .touches
и .targetTouches
. Свойство .touches
всегда будет возвращать список TouchList
всех трех пальцев. Вне зависимости от того мы двигаем всеми тремя пальцами или только первым или только третьим. .targetTouches
также как и .touches
всегда будет возвращать список пальцев, но только те которые находятся внутри элемента, в данном случае 1 и 2 пальцы, даже если мы будем двигать третьим пальцем вернет 1 и 2 пальцы. И последнее свойство .changedTouches
возвращает те пальцы, которые изменили свое положение и которые были внутри элемента. Например, мы будем двигать только 1 и 3 пальцами, а 2 будет неподвижно прикасаться, то тогда вернет только 1 палец, потому что он двигается и находится внутри элемента.
Объект прикосновение Touch
Как уже было сказано выше три свойства .changedTouches
, .touches
и .targetTouches
возвращают TouchList
это массив всех прикосновений который содержит объекты с интерфейсом Touch
Тут мы поговорим подробнее о том какие свойства он принимает.
Идентификатор
.identifier
: данное свойство указывает уникальный id каждого прикосновения, таким образом вы всегда однозначно сможете определять каждое прикосновение пальца.
Координаты
Всего шесть свойств связанные с координатами есть у каждого объекта Touch
Вот что мы имеем .screenX
, .screenY
, .clientX
, .clientY
, .pageX
, .pageY
.clientX
,.clientY
Как видно из картинки выше данные координаты отсчитываются от верхнего левого угла вашего окна..screenX
,.screenY
Здесь уже точка отсчета координат от вашего экрана, на примере видно что часть вверху идет черная полоска, где показывается время затем идет поисковая строка, затем только окно сайта. В данных координатах все это учитываются..pageX
,.pageY
За точку отсчета мы берем начала вашего документа то есть по сути это сумма скролла документа и координаты от окнаwindow.scrollTop + e.clientY
Область касания
Сначала я хочу сразу оговориться что не все девайсы поддерживают данную функцию. Вы можете использовать эту функцию, предполагая, что у ваших пользователей продвинутые устройства поддерживающие распознавание области касания. Если особой необходимости нету, то лучше избежать этого. Как это работает: в объекте Touch также есть четыре свойства отвечающие за это .radiusX
, .radiusY
, .rotationAngle
, .force
. Любое прикосновение обрабатывается как эллипс в javascript даже если вы попробуете прикоснуться квадратным стилусом, это будет эллипс. Как вы можете понять из картинки эллипс описывается малой полуосью a, большой полуосью b и углом поворота эллипса alpha данным значениям соответствуют своства соответственно .radiusY
, .radiusX
и .rotationAngle
Также есть свойство .force
которое принимает значение от 0 до 1 и указывает с какой силой вы давите на экран, но еще раз повторюсь далеко не все устройства поддерживают данное свойство и как правило всегда возвращает значение 0.
Всем спасибо. Ссылка на github
Комментарии (7)
Bigata
30.11.2021 10:33И хорошо, что разобрались. Как поверх реакта имплементировали?
Bookatich
05.12.2021 14:14+1Ссылка на гитхаб с имплементацией в самом начале статьи:
https://github.com/fakt309/imager
noodles
04.12.2021 17:11+1const isTouch = () => 'ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0 el.addEventListener('mousedown', (e) => { if (isTouch()) return // если ваш экран поддерживает touch то взвращаем функцию console.log('click') });
Чуть оптимизировал)
const isTouch = 'ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0 el.addEventListener('mousedown', (e) => { if (isTouch) return // если ваш экран поддерживает touch то взвращаем функцию console.log('click') });
random13
Нашел опечатку, touchend.