Ссылка на github

Вступление

Недавно я писал компонент для 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)


  1. random13
    30.11.2021 10:30
    +2

    touchstart - работает по точно такому же принципу как и `touchstart`, но когда вы убираете палец с экрана

    Нашел опечатку, touchend.


  1. Bigata
    30.11.2021 10:33

    И хорошо, что разобрались. Как поверх реакта имплементировали?


    1. Bookatich
      05.12.2021 14:14
      +1

      Ссылка на гитхаб с имплементацией в самом начале статьи:
      https://github.com/fakt309/imager


  1. eeeMan
    30.11.2021 15:20

    тачстарт работает так же как тачстарт, втф?


  1. tsepen
    30.11.2021 15:20
    +1

    Опечатка, имелось ввиду touchend


  1. noodles
    04.12.2021 17:11
    +1

    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')
    });

    Чуть оптимизировал)

    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')
    });


  1. Hugoist
    07.12.2021 22:29
    +1

    Вот владельцы ноутов с тачскринами удивятся