Сенсорные жесты

Подготовили: Анна Лысак и Татьяна Головко Дата публикации: 13.07.2011

Термин gesture — способ объединить движения пальцем по экрану для запуска какого-то действия; движение пальцем в таком случае используется вместо просто касания или клика. Полное касание (complete touch) или mouse — move-capturing функция — нужна для того, чтобы жесты регистрировались и были абсолютно правильными. Сегодня хорошая поддержка этой функции есть только в браузерах Safari и Android.

Если в твоем веб-приложении пользователю нужно использовать жесты, важно научить его правильным действиям — при помощи справки, анимированных примеров или какого-нибудь другого вида подсказок (рисунок 8.5).

Google Fast Flip новый просмотрищик, использующий жесты на iPhone и Android устройств. Слева вы увидите предупреждающий диалог с инструкциями о том, как его использовать. Вы увидите инструкции только один раз Рис. 8.5. Google Fast Flip новый просмотрищик, использующий жесты на iPhone и Android устройств. Слева вы увидите предупреждающий диалог с инструкциями о том, как его использовать. Вы увидите инструкции только один раз.

Жест Swipe

Жест swipe (также известен как flip) — технология для тач-браузеров, обычно используется для перемещения контента вперед-назад. Этот жест используется, например, во многих фото-галереях для смены выведенного на экран изображения и в презентациях для перелистывания слайдов. Суть жеста — простое движение пальцем по оси Х слева направо (горизонтальный swipe) или по оси Y сверху вниз (вертикальный swipe). swipe-жест поддерживается практически в каждом сенсорном устройстве, так как осуществляется одним пальцем.

Для перехвата swipe-действия нет специального стандартного события, поэтому будем эмулировать его, используя имеющиеся стандартные события

В устройствах Symbian 5-го поколения если вместо курсора использовать палец, то для событий mouse down, move и up получаются довольно странные результаты. Событие <onmousemove> генерируется только один раз во время действия пальцем «перетащить» (drag), а событие <onmouseup> вообще не срабатывает, если палец перемещен из точки начальных mouse-down координат. Поэтому, для обнаружения swipe в некоторых ситуациях нужны разные подходы.

Последовательность действий:

  1. Перехват события <onmousedown> (или ontouchstart в iPhone и других совместимых браузерах) и старт записи жеста.
  2. Перехват <onmousemove> (или ontouchmove в iPhone и браузерах с необходимой поддержкой) и продолжение записи жеста, если перемещение по оси Х (или Y) происходит в пределах определенного порога. Отмена жеста, если перемещение происходит по другой оси.
  3. Перехват onmouseup (или ontouchend в iPhone и браузерах с необходимой поддержкой) и, если в этот момент жест продолжался (был активным) и разница между исходными и конечными координатами больше, чем определенная константа - определите swipe в одном направлении.

Последний пункт может быть заменен проверкой жеста на-лету внутри события onmousemove.

Если ты используешь в работе jQuery, можно для обнаружения горизонтального жеста swipe на устройствах iPhone использовать бесплатный плагин отсюда http://plugins.jquery.com/project/swipe.

При помощи следующего кода мы можем создать объектно-ориентированную библиотеку для обнаружения swipe (совместимо iPhone, Android и другими устройствами):

/**
Creates a swipe gesture event handler
*/
function MobiSwipe(id) {
// Constants
this.HORIZONTAL = 1;
this.VERTICAL = 2;
this.AXIS_THRESHOLD = 30; // The user will not define a perfect line
this.GESTURE_DELTA = 60; // The min delta in the axis to fire the gesture
// Public members
this.direction = this.HORIZONTAL;
this.element = document.getElementById(id);
this.onswiperight = null;
this.onswipeleft = null;
this.onswipeup = null;
this.onswipedown = null;
this.inGesture = false;
// Private members
this._originalX = 0
this._originalY = 0
var _this = this;
// Makes the element clickable on iPhone
this.element.onclick = function() {void(0)};
var mousedown = function(event) {
// Finger press
event.preventDefault();
_this.inGesture = true;
_this._originalX = (event.touches) ? event.touches[0].pageX : event.pageX;
_this._originalY = (event.touches) ? event.touches[0].pageY : event.pageY;
// Only for iPhone
if (event.touches && event.touches.length!=1) {
_this.inGesture = false; // Cancel gesture on multiple touch
}
};
var mousemove = function(event) {
// Finger moving
event.preventDefault();
var delta = 0;
// Get coordinates using iPhone or standard technique
var currentX = (event.touches) ? event.touches[0].pageX : event.pageX;
var currentY = (event.touches) ? event.touches[0].pageY : event.pageY;
// Check if the user is still in line with the axis
if (_this.inGesture) {
if ((_this.direction==_this.HORIZONTAL)) {
delta = Math.abs(currentY-_this._originalY);
} else {
delta = Math.abs(currentX-_this._originalX);
}
if (delta >_this.AXIS_THRESHOLD) {
// Cancel the gesture, the user is moving in the other axis
_this.inGesture = false;
}
}
// Check if we can consider it a swipe
if (_this.inGesture) {
if (_this.direction==_this.HORIZONTAL) {
delta = Math.abs(currentX-_this._originalX);
if (currentX>_this._originalX) {
direction = 0;
} else {
direction = 1;
}
} else {
delta = Math.abs(currentY-_this._originalY);
if (currentY>_this._originalY) {
direction = 2;
} else {
direction = 3;
}
}
if (delta >= _this.GESTURE_DELTA) {
// Gesture detected!
var handler = null;
switch(direction) {
case 0: handler = _this.onswiperight; break;
case 1: handler = _this.onswipeleft; break;
case 2: handler = _this.onswipedown; break;
case 3: handler = _this.onswipeup; break;
}
if (handler!=null) {
// Call to the callback with the optional delta
handler(delta);
}
_this.inGesture = false;
}
}
};
// iPhone and Android's events
this.element.addEventListener('touchstart', mousedown, false);
this.element.addEventListener('touchmove', mousemove, false);
this.element.addEventListener('touchcancel', function() {
_this.inGesture = false;
}, false);
// We should also assign our mousedown and mousemove functions to
// standard events on compatible devices
}

Вот простой пример использования нашей бибилотеки swipe.js с <div> с горизонтальным обнаружением swipe и другим <div> с вертикальным обнаружением:

<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN"
"http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<title>Swipe Gesture Detection</title>
		<meta name="viewport" content="width=device-width; initial-scale=1.0;
		maximum-scale=1.0; user-scalable=0;">
		<script type="text/javascript" src="swipe.js"></script>
		<script type="text/javascript">
			window.onload = function() {
			var swipev = new MobiSwipe("vertical");
			swipev.direction = swipev.VERTICAL;
			swipev.onswipedown = function() { alert('down'); };
			swipev.onswipeup = function() { alert('up'); };
			var swipeh = new MobiSwipe("horizontal");
			swipeh.direction = swipeh.HORIZONTAL;
			swipeh.onswiperight = function() { alert('right'); };
			swipeh.onswipeleft = function() { alert('left'); };
		}
		</script>
	</head>
	<body>
		<div style="width: 100%; height: 150px; background-color: blue" id="vertical">
			Vertical Swipe
		</div>
		<div style="width: 100%; height: 150px; background-color: red" id="horizontal">
			Horizontal Swipe
		</div>
	</body>
</html>

Во многих сенсорных устройствах жест «перетащить» (drag) используется для прокрутки содержимого страницы и, при этом, не поддерживается функция preventDefault (о функции предотвращения поведения по умолчанию мы говорили чуть ранее в этой главе). Именно поэтому мы должны помимо жеста swipe рассмотреть и другие доступные способы навигации.

Жесты машстабирования и поворота

Когда iPhone только появился, самыми крутыми функциями в нем были именно жесты изменения масштаба и поворота. Используя жест pinching (сдвигая и раздвигая два пальца в щепотке) пользователь мог увеличить или уменьшить масштаб контента — это, как правило, изображение — на странице, а поворачивая два пальца по кругу изображение можно повернуть.

В устройствах, где нет поддержки мультитач функции изменения масштаба нужно реализовывать при помощи обычных плавающих кнопок и ползунков.

К счастью, начиная с iOS 2.0 эти жесты можно обнаружить не прибегая с низкоуровневой математике в тач-событиях . В таблице 8.35 перечислены три расширения WebKit, которые доступны в качестве событий. В браузере Android также добавлена поддержка этих событий.

Табл. 8.35. События доступные для сенсорной обработки
Событие Описание
ongesturestart Появляется, когда пользователь начинает жест двумя пальцами
ongesturechange Появляется, когда пользователь касается пальцем, поворачивает или защемляет
ongestureend Появляется, когда пользователь двигает вверх один или оба пальца

Для масштабирования и поворота используются эти же события. Все три получают параметр GestureEvent. У этого параметра есть типичные для события свойства, а также дополнительные свойства scale и rotation.

Свойство scale определяет расстояние между двумя пальцами как множитель с плавающей точкой от начала дистанции, где было начало жеста. Если значение больше 1.0, значит это открытый pinch (увеличение), а если значение 1.0 — pinch закрытия (уменьшение).

rotation дает значение (в градусах) дельты (расстояния) вращения от начальной точки. Если пользователь вращает объект по часовой стрелке, то мы получаем положительное значение, а если против — то значение будет отрицательным.

Я знаю, что ты сейчас думаешь: «Вращение и изменение масштаба — это отлично, но какой нам от них толк, если мы работаем с HTML?». Здесь нам приходят на помощь CSS расширения для Safari на iOS (и в других браузеров с соответствующей поддержкой) с одним свойством -webkit-transform и двумя функциями для управлением его значением: rotate и scale.

Функция rotate получает параметр в градусах и нам нужно определить deg unit после числа (например, rotate(90deg)). Узнать это мы можем из скрипта при помощи element.style.webkitTransform.

Давай рассмотри простой пример:

<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN"
"http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<title>Gesture Management</title>
		<meta name="viewport" content="width=device-width; initial-scale=1.0;
		maximum-scale=1.0; user-scalable=0;">
		<script type="text/javascript">
			function gesture(event) {
				// We round values with two decimals
				event.target.innerHTML = "Rotation: " + Math.round(event.rotation*100)/100
				+ " Scale: " + Math.round(event.scale*100)/100;
				// We apply the transform functions to the element
				event.target.style.webkitTransform = "rotate(" + event.rotation%360 + "deg)" +
				" scale(" + event.scale + ")";
			}
		</script>
	</head>
	<body>
		<div ongesturechange="gesture(event)" style="background-color:silver; width: 300px;
		height: 300px">
		</div>
	</body>
</html>

Как работает пример можешь увидеть на Рисунке 8-6. На совместимых устройствах ты можешь двумя пальцами поворачивать и масштабировать <div> (вместе со всем содержимым). Только вот в чем проблема? Стиль преобразования всегда применяется к исходному элементу. Так, если мы применим к элементу масштаб 2.0, а потом еще раз увеличим на 0.5, то новое значение будет 0.5, а не 1.0, как можно было бы ожидать.

Сочетая сенсорные события с CSS трансформациями, можно вращать и масштабировать элементы на своем сайте Рис. 8.6. Сочетая сенсорные события с CSS трансформациями, можно вращать и масштабировать элементы на своем сайте.

Для типичного zoom-rotate поведения мы должны заменить функцию на следующее:

<script type="text/javascript">
	var rotation = 0;
	var scale = 1;
	function gesture(event) {
		event.target.innerHTML = "Rotation: " +
		Math.round((event.rotation+rotation)*100)/100
		+ " Scale: " + Math.round((event.scale*scale)*100)/100;
		event.target.style.webkitTransform = "rotate(" + (event.rotation+rotation)%360
		+ "deg)" + " scale(" + event.scale*scale + ")";
	}
	function gestureend(event) {
		rotation = event.rotation+rotation;
		scale = event.scale*scale;
	}
</script>
</head>
<body>
	<div ongesturechange="gesture(event)" ongestureend="gestureend(event)"
	style="background-color:silver; width: 100%; height: 300px">
	</div>

Куда дальше

Показать комментарии