Touch события на javascript, мультитач - реализация
- Подробности
- Категория: Разработка
- Опубликовано 17.09.2014 20:19
Разрабатывая приложения для Android и iPhone с iPad (IOS) используя лишь веб-технологии, перед каждым девелопером встаёт рано или поздно вопрос реализации и использования в своей игре или приложении сложными мультитач действиями, такими как свайп, щепотка, а также обработки длительного тапа (длительное касание одним пальцем без движения) и drag'n'drop.
В этом материале мы рассмотрим способы осуществления обработки тач-событий разной сложности на Javascript Event'ах, без сторонних библиотек (jQuery min только прихватим).
Сразу оговорюсь, что использовать jQuery я буду только для того чтобы отключить дефолтное поведение браузера на событие. Мне не нравится эта библиотека, поэтому всё будет написано на Vanilla JS.
Итак, начнём мы с теории и собственно коснёмся того какие события стоит использовать при создании приложения или игры.
Всего используются 3 ключевых события:
touchstart - Коснулись экрана
touchend - Палец убрали
touchmove - Двигаем пальцем
Если чтобы получить элемент на который заехала мышь или съехала, было достаточно вызвать event.target, то с touch-событиями не так всё просто.
Каждое касание должно идентифицироваться. И для этого используются списки:
touches - Все пальцы которые сейчас взаимодействуют с экраном ("Коснуты" экрана)
targetTouches - Список пальцев которые взаиможействуют с элементом
changedTouches - Список пальцев, которые учавствуют в текущем событии. Если например это событие touchend, то список будет содержать палец который был убран (Даже если остальные 4 пальца до сих пор на экране).
Чтобы было проще понять вот ситуация:
Я ставлю один палец на экран и все 3 списка имеют один элемент.
Я ставлю второй палец и теперь touches имеет 2 элемента, targetTouches будет иметь 2 элемента если второй палец я поставлю на тот же HTML элемент что и первый, а changedTouches в свою очередь будет иметь только второй палец, так как именно он вызвал событие.
Если я поставлю на экран сразу 2 пальца одновременно, тогда changedTouches будет иметь 2 элемента (по каждому на палец).
Если я начну двигать своими пальцами по экрану, то будет меньться только список changedTouches. Количество элементов которое он будет содержать будет равняться количеству пальцев задействованных в движение (как минимум 1).
Если я уберу палец, то списки touches, targetTouches опустеют на один элемент, а changedTouches будет содердать палец, так как он вызвал событие (touchend)
Когда я уберу последний палец, списки touches, targetTouches не будут содержать ничего, а changedTouches будет иметь информацию о этом самом пальце.
Теперь самое время узнать какую именно информацию мы можем получить о пальце:
identifier - Уникальный ID касания
target - Сам объект к которому мы коснулись
PageX,PageY - Координаты касания на странице
Посмотреть уникальный ID единственного касания можно вызвав event.touches[0].identifier, а на IOS, если я не ошибаюсь надо делать так event.originalEvent.touches[0].identifier.
Что ж, кой чему я вас уже научил, а теперь самое время перейти к практике.
Перед тем как мы приступим вам следует усвоить кое-что. В каждой игре, приложении которое вы будете делать на Android и IOS вы должны отключить стандартную реакцию компонента WebView на события. Для этого мы и подключали jQuery (Я не смог сделать на Pure JS то что делают функции event.preventDefault() и event.stopPropagation()).
Итак вам нужно поместить в ваш JS код следующее:
document.addEventListener('touchstart', function(event) {
event.preventDefault();
event.stopPropagation();
/* Здесь ваш код обработки события*/
}, false);
document.addEventListener('touchmove', function(event) {
event.preventDefault();
event.stopPropagation();
/* Здесь ваш код обработки события*/
}, false);
document.addEventListener('touchend', function(event) {
event.preventDefault();
event.stopPropagation();
/* Здесь ваш код обработки события*/
}, false);
Это обязательно нужно делать, потому что у многих устройств без этого существенные тормоза глюки и дёрганная анимация.
Отвелеклись. Продолжаем.
Давайте имея полученные знания опишем все основные touch взаимодействия с экраном и элементами.
Tap (Косание)
Это событие выполняется просто когда мы косаемся пальцем экрана и убираем его.
obj.addEventListener('touchstart', function(event) {
if (event.targetTouches.length == 1) {
var myclick=event.targetTouches[0]; /*Ваш код*/
}
}, false);
На примере в переменной myclick будет содержаться ваше касание.
Long tap (Длительное косание)
Хоть и нечасто, однако встречаются ситуации когда в мобильном приложении или игре нужно поймать действие длительного касания на объекте. Давайте рассмотрим как сделать длительное касание на Javascript для сенсорных экранов мобильных смартфонов ну иконечно же планшетов.
Реализация 1:
var ldelay;
var betw={};
document.addEventListener('touchstart', function(event) {
event.preventDefault();
event.stopPropagation();
ldelay=new Date();
betw.x=event.changedTouches[0].pageX;
betw.y=event.changedTouches[0].pageY;
}, false);
/*Ловим отпускание пальца*/
document.addEventListener('touchend', function(event) {
var pdelay=new Date();
if(event.changedTouches[0].pageX==betw.x &&
event.changedTouches[0].pageY==betw.y &&
(pdelay.getTime()-ldelay.getTime())>800){
/*Здесь ваш код*/
}
}, false);
Это первая реализация Long Tap на яваскрипте. Логика такая: ловим касание, замеряем время этого события, ловим отпускание, замеряем время отпускания, вычитаем первое время из второго и проверяем не изменилось ли положения пальца на экране. Если палец на том же месте и времени прошло более 800 миллисекунд, мы выполняем действия Long Tap.
Теперь давайте разберём вторую реализацию с немного другой логикой:
Реализация 2:
var lttimer;
document.addEventListener('touchstart', function(event) {
event.preventDefault();
event.stopPropagation();
lttimer=setTimeout(longTouch,800);
}, false);
document.addEventListener('touchmove', function(event) {
event.preventDefault();
event.stopPropagation();
clearTimeout(lttimer);
}, false);
document.addEventListener('touchend', function(event) {
clearTimeout(lttimer);
}, false);
function longTouch(){/*Ваш код*/}
Выше приведённая реализация события Long Tap на Javascript является более правильной и наиболее часто применяется в мобильных приложениях. Её главное отличие от предыдущей реализации в том что она не дожидается когда будет отпущен палец и сама в том случае если палец не двигался запускает событие Long Tap, которое вы должны поместить в функцию longTouch.longTouch.
Swipe (Листание)
Самое время поговорить о листании на экране смартфона или планшета. Свайп - довольно распространённый в построении в первую очередь мобильных приложений, поэтому рано или поздно с ним приходиться иметь дело каждому аппстроителю.
Если вы не хотите заморачиваться и вам нужен только функционал Swipe в мобильном приложении, вы можете почитать в самом низу страницы об этом.
Если же вы зардкорный девелопер - поехали!
Реализация 1:
var initialPoint;
var finalPoint;
document.addEventListener('touchstart', function(event) {
event.preventDefault();
event.stopPropagation();
initialPoint=event.changedTouches[0];
}, false);
document.addEventListener('touchend', function(event) {
event.preventDefault();
event.stopPropagation();
finalPoint=event.changedTouches[0];
var xAbs = Math.abs(initialPoint.pageX - finalPoint.pageX);
var yAbs = Math.abs(initialPoint.pageY - finalPoint.pageY);
if (xAbs > 20 || yAbs > 20) {
if (xAbs > yAbs) {
if (finalPoint.pageX < initialPoint.pageX){
/*СВАЙП ВЛЕВО*/}
else{
/*СВАЙП ВПРАВО*/}
}
else {
if (finalPoint.pageY < initialPoint.pageY){
/*СВАЙП ВВЕРХ*/}
else{
/*СВАЙП ВНИЗ*/}
}
}
}, false);
Это у нас первая реализация свайпа на Javascript. Особенность этой реализации в том что событие засчитывается как свайп, когда вы отпускаете палец участвующий в событии. Данный свайп может быть применён в некоторых задачах. Не забывайте в этом и многих других премерах выключать стандартное поведение браузера на тач события (Об этом я писал выше), я не пишу обычно их в приведённых примерах, но вы не забывайте.
А теперь рассмотрим другую - классическую реализацию, когда swipe нужно считать в реальном времени, например при перелистывании страницы:
var startPoint={};
var nowPoint;
var ldelay;
document.addEventListener('touchstart', function(event) {
event.preventDefault();
event.stopPropagation();
startPoint.x=event.changedTouches[0].pageX;
startPoint.y=event.changedTouches[0].pageY;
ldelay=new Date();
}, false);
/*Ловим движение пальцем*/
document.addEventListener('touchmove', function(event) {
event.preventDefault();
event.stopPropagation();
var otk={};
nowPoint=event.changedTouches[0];
otk.x=nowPoint.pageX-startPoint.x;
/*Обработайте данные*/
/*Для примера*/
if(Math.abs(otk.x)>200){
if(otk.x<0){/*СВАЙП ВЛЕВО(ПРЕД.СТРАНИЦА)*/}
if(otk.x>0){/*СВАЙП ВПРАВО(СЛЕД.СТРАНИЦА)*/}
startPoint={x:nowPoint.pageX,y:nowPoint.pageY};
}
}, false);
/*Ловим отпускание пальца*/
document.addEventListener('touchend', function(event) {
var pdelay=new Date();
nowPoint=event.changedTouches[0];
var xAbs = Math.abs(startPoint.x - nowPoint.pageX);
var yAbs = Math.abs(startPoint.y - nowPoint.pageY);
if ((xAbs > 20 || yAbs > 20) && (pdelay.getTime()-ldelay.getTime())<200) {
if (xAbs > yAbs) {
if (nowPoint.pageX < startPoint.x){/*СВАЙП ВЛЕВО*/}
else{/*СВАЙП ВПРАВО*/}
}
else {
if (nowPoint.pageY < startPoint.y){/*СВАЙП ВВЕРХ*/}
else{/*СВАЙП ВНИЗ*/}
}
}
}, false);
В этом способе мы пошли немного другим путём и чстично использовали принцип первой реализации. По своей логике это немного более сложный свайп. В том месте где откомментировано /*Обработайте данные*/, вы должны использовать координаты свайпающего пальца. Например, это может быть анимация перелистывания страницы и чем левее палец тем больше отлистывается страница. Для примера мы в той части слушателя события touchmove отслеживали только x-координату, y - прикручивается подобным образом. В переменной otk.x храниться текущее положение пальца относительно точки где он впервые коснулся эрана. Если палец левее этой точки то переменная имеет отрицательное значение, если правее - положительное.
Для примера мы там поставили условие когда палец перемещается на расстояние более 200 пикселей в лево или право от точки касания - мы засчитываем свайп. Для чего это нужно? Например вы можете как только пользователь коснулся и начал двигать палец показывать ему плавное перелистывание страницы, которая идёт за его пальцем, а как только палец уходит за 200 пикселей, совершает окончательная анимация и страница перелистывается. Это как одна из возможных способом применения такого свайпа.
Но зачем тогда событие touchend спросите вы... Иногда пользователь не желает на какое -то расстояние перемещать свой палец для свайпа и во многих приложениях используется свайп как реакция на быстрое перемещение пальца по экрану в сторону на небольшое расстояние. Именно это мы и применили в последнем слушателе событий.
Drag'n'Drop (Перетаскивание элемента)
Нередко в интерфейсах приложений и в играх приходится перетаскивать пальцем один из элементов в определённое место. Давайте сделаем это на javascript заточенным под сенсорные экраны. Начнём:
var obj = document.getElementById('sat');
/*Ловим касание*/
obj.addEventListener('touchstart', function(event) {
if (event.targetTouches.length == 1) {
var touch=event.targetTouches[0];
touchOffsetX = touch.pageX - touch.target.offsetLeft;
touchOffsetY = touch.pageY - touch.target.offsetTop;
}
}, false);
/*Передвигаем объект*/
obj.addEventListener('touchmove', function(event) {
if (event.targetTouches.length == 1) {
var touch = event.targetTouches[0];
obj.style.left = touch.pageX-touchOffsetX + 'px';
obj.style.top = touch.pageY-touchOffsetY + 'px';
}
}, false);
Как видно это не весь код, у нас пока получился drag без drop'а. Вы уже наверняка обратили внимание что передвижение элемента в конечном счёте производится css параметрами left и top. Можно вместо этих двух строчек, отвечающих за передвижение объкта по экрану поставить:
obj.style.WebkitTransform="translate("+(touch.pageX-touchOffsetX)+"px,"+(touch.pageY-touchOffsetY)+"px)";
Т.е. использовать CSS3, однако в своём случае я не заметил прироста производительности, пожтому лувше top и left. Этим кодом вы можете передвигать предмет по полю, но его отпускание на каком-то определённом месте засчитано не будет. Чтобы это реализовать добавим на объект обработчик touchend с соответствующим кодом внутри:
var tarobj=document.getElementById('dro');
obj.addEventListener('touchend', function(event) {
if (event.changedTouches.length == 1) {
var tarWidth=tarobj.offsetWidth;
var tarHeight=tarobj.offsetHeight;
var tarX=tarobj.offsetLeft;
var tarY=tarobj.offsetTop;
if(
(event.changedTouches[0].pageX > tarX) &&
(event.changedTouches[0].pageX < (tarX + tarWidth)) &&
(event.changedTouches[0].pageY > tarY) &&
(event.changedTouches[0].pageY < (tarY + tarHeight))){
/*Мы над объектом tarobj*/
}
}
}, false);
Для того чтобы всё работало, укажите ваш объект в переменную tarobj и будет вам счастье.
Pitch (Щепотка)
Настало время вспомнить знаменитую щепотку, которую впервые показал Стив Джобс, когда презентовал первый айфон. Именно это движение двумя пальцами друг к друг другу или друг от друг от друга и называется щепоткой. Обычно этот жест используется для увеличения или уменьшения чего-либо.
В примере ниже отмечены комментариями ключевые моменты (чтобы код не сливался в полнейшую пшеничную кашу):
/*Определяем некоторые переменные*/
var objzoom = document.getElementById("dro");
var scaling = false;
var dist = 0;
var scale_factor = 1.0;
var curr_scale = 1.0;
var max_zoom = 8.0;
var min_zoom = 0.5
/*Пишем функцию, которая определяет расстояние меж пальцами*/
function distance (p1, p2) {
return (Math.sqrt(Math.pow((p1.clientX - p2.clientX), 2) + Math.pow((p1.clientY - p2.clientY), 2)));
}
/*Ловим начало косания*/
objzoom.addEventListener('touchstart', function(evt) {
evt.preventDefault();
var tt = evt.targetTouches;
if (tt.length >= 2) {
dist = distance(tt[0], tt[1]);
scaling = true;
} else {
scaling = false;
}
}, false);
/*Ловим зумирование*/
objzoom.addEventListener('touchmove', function(evt) {
evt.preventDefault();
var tt = evt.targetTouches;
if (scaling) {
curr_scale = distance(tt[0], tt[1]) / dist * scale_factor;
objzoom.style.WebkitTransform = "scale(" + curr_scale + ", " + curr_scale + ")";
}
}, false);
/*Ловим конец косания*/
objzoom.addEventListener('touchend', function(evt) {
var tt = evt.targetTouches;
if (tt.length < 2) {
scaling = false;
if (curr_scale < min_zoom) {
scale_factor = min_zoom;
} else {
if (curr_scale > max_zoom) {
scale_factor = max_zoom;
} else {
scale_factor = curr_scale;
}
}
objzoom.style.WebkitTransform = "scale(" + scale_factor + ", " + scale_factor + ")";
} else {
scaling = true;
}
}, false);
В примере мы используем для теста объект с id dro, вы можете использовать свой объект, прописав его переменной objzoom. В переменных вы можете поменять данные, например максимальный зум или минимальный.
Слайдинг изображений и HTML
Мы уже выше описали ка сделать swipe на основе которого можно сделать слайдинг изображений или своих данных в HTML коде. Однако некоторых может не устроить такое и если вы в числе тех кто хочет проще и быстрее получить результат не вдаваясь в подробности, для вас этот подраздел.
Первым делом отметим бесплатную JS-разработку под названием
Есть также красивый слайдинг изображений
Помимо такого мануального создания тач-оболочки приложения или игры, вы можете использовать фреймворк. Вот список популярных в этой теме фреймворков
Последний вопрос - вопрос совместимости этого всего с мобильными платформами. Что ж, touch поддерживают Android 2.3-4.X и IOS. А вот мультитач поддерживают все кроме Android 2.3.
Не забывайте что вешать обработчики на HTML-объекты стоит тогда когда они уже известны, т.е. в событии window.onload или DOMContentLoaded.
Если есть чем дополнить статью - пишите в комментариях.