Как вы можете обнаружить, что пользователь провел пальцем в каком-то направлении по веб-странице с помощью JavaScript?
Мне было интересно, есть ли одно решение, которое будет работать на сайтах как на iPhone, так и на Android-телефоне.
Как вы можете обнаружить, что пользователь провел пальцем в каком-то направлении по веб-странице с помощью JavaScript?
Мне было интересно, есть ли одно решение, которое будет работать на сайтах как на iPhone, так и на Android-телефоне.
Простой ванильный пример кода JS:
document.addEventListener('touchstart', handleTouchStart, false);
document.addEventListener('touchmove', handleTouchMove, false);
var xDown = null;
var yDown = null;
function getTouches(evt) {
return evt.touches || // browser API
evt.originalEvent.touches; // jQuery
}
function handleTouchStart(evt) {
const firstTouch = getTouches(evt)[0];
xDown = firstTouch.clientX;
yDown = firstTouch.clientY;
};
function handleTouchMove(evt) {
if ( ! xDown || ! yDown ) {
return;
}
var xUp = evt.touches[0].clientX;
var yUp = evt.touches[0].clientY;
var xDiff = xDown - xUp;
var yDiff = yDown - yUp;
if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
if ( xDiff > 0 ) {
/* left swipe */
} else {
/* right swipe */
}
} else {
if ( yDiff > 0 ) {
/* up swipe */
} else {
/* down swipe */
}
}
/* reset values */
xDown = null;
yDown = null;
};
Проверено в Android.
Я нашел этот плагин jquery touchwipe, который работает как на моем первом iPod touch, так и на моем дроиде. http://www.netcu.de/jquery-touchwipe-iphone-ipad-library
Основываясь на ответе @givanse, вы можете сделать это с помощью classes
:
class Swipe {
constructor(element) {
this.xDown = null;
this.yDown = null;
this.element = typeof(element) === 'string' ? document.querySelector(element) : element;
this.element.addEventListener('touchstart', function(evt) {
this.xDown = evt.touches[0].clientX;
this.yDown = evt.touches[0].clientY;
}.bind(this), false);
}
onLeft(callback) {
this.onLeft = callback;
return this;
}
onRight(callback) {
this.onRight = callback;
return this;
}
onUp(callback) {
this.onUp = callback;
return this;
}
onDown(callback) {
this.onDown = callback;
return this;
}
handleTouchMove(evt) {
if ( ! this.xDown || ! this.yDown ) {
return;
}
var xUp = evt.touches[0].clientX;
var yUp = evt.touches[0].clientY;
this.xDiff = this.xDown - xUp;
this.yDiff = this.yDown - yUp;
if ( Math.abs( this.xDiff ) > Math.abs( this.yDiff ) ) { // Most significant.
if ( this.xDiff > 0 ) {
this.onLeft();
} else {
this.onRight();
}
} else {
if ( this.yDiff > 0 ) {
this.onUp();
} else {
this.onDown();
}
}
// Reset values.
this.xDown = null;
this.yDown = null;
}
run() {
this.element.addEventListener('touchmove', function(evt) {
this.handleTouchMove(evt).bind(this);
}.bind(this), false);
}
}
Вы можете использовать его следующим образом:
// Use class to get element by string.
var swiper = new Swipe('#my-element');
swiper.onLeft(function() { alert('You swiped left.') });
swiper.run();
// Get the element yourself.
var swiper = new Swipe(document.getElementById('#my-element'));
swiper.onLeft(function() { alert('You swiped left.') });
swiper.run();
// One-liner.
(new Swipe('#my-element')).onLeft(function() { alert('You swiped left.') }).run();
Вы пробовали hammer.js? http://eightmedia.github.com/hammer.js/ Также работает на телефонах Windows.
что я использовал раньше, вам нужно обнаружить событие mousedown, записать его местоположение x, y (в зависимости от того, что имеет значение), затем обнаружить событие mouseup и вычесть два значения.
jQuery Mobile также включает поддержку прокрутки: http://api.jquerymobile.com/swipe/
Пример
$("#divId").on("swipe", function(event) {
alert("It a swipe!");
});
Я нашел блестящий ответ @givanse самым надежным и совместимым в нескольких мобильных браузерах для регистрации действий салфетки.
Однако в его коде требуется изменение кода, чтобы он работал в современных мобильных браузерах, которые используют jQuery
.
event.touches
не будет существовать, если используется jQuery
и результат undefined
и должен быть заменен на event.originalEvent.touches
. Без jQuery
, event.touches
должно работать нормально.
Таким образом, решение становится,
document.addEventListener('touchstart', handleTouchStart, false);
document.addEventListener('touchmove', handleTouchMove, false);
var xDown = null;
var yDown = null;
function handleTouchStart(evt) {
xDown = evt.originalEvent.touches[0].clientX;
yDown = evt.originalEvent.touches[0].clientY;
};
function handleTouchMove(evt) {
if ( ! xDown || ! yDown ) {
return;
}
var xUp = evt.originalEvent.touches[0].clientX;
var yUp = evt.originalEvent.touches[0].clientY;
var xDiff = xDown - xUp;
var yDiff = yDown - yUp;
if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
if ( xDiff > 0 ) {
/* left swipe */
} else {
/* right swipe */
}
} else {
if ( yDiff > 0 ) {
/* up swipe */
} else {
/* down swipe */
}
}
/* reset values */
xDown = null;
yDown = null;
};
Протестировано:
Я объединил несколько ответов здесь в сценарий, который использует CustomEvent для запуска свипированных событий в DOM. Добавьте скрипт 0.7k swiped -events.min.js на свою страницу и прослушайте события swiped:
document.addEventListener('swiped-left', function(e) {
console.log(e.target); // the element that was swiped
});
document.addEventListener('swiped-right', function(e) {
console.log(e.target); // the element that was swiped
});
document.addEventListener('swiped-up', function(e) {
console.log(e.target); // the element that was swiped
});
document.addEventListener('swiped-down', function(e) {
console.log(e.target); // the element that was swiped
});
Вы также можете прикрепить непосредственно к элементу:
document.getElementById('myBox').addEventListener('swiped-down', function(e) {
console.log(e.target); // the element that was swiped
});
Вы можете указать следующие атрибуты для настройки функций взаимодействия смахиванием на вашей странице (они необязательны).
<div data-swipe-threshold="10"
data-swipe-timeout="1000"
data-swipe-ignore="false">
Swiper, get swiping!
</div>
Исходный код доступен на Github
Я переупаковал TouchWipe
как короткий плагин jquery: detectSwipe
Некоторая мода ответа на ответ (не могу комментировать...), чтобы иметь дело с короткими стрелками
document.addEventListener('touchstart', handleTouchStart, false);
document.addEventListener('touchmove', handleTouchMove, false);
var xDown = null;
var yDown = null;
function handleTouchStart(evt) {
xDown = evt.touches[0].clientX;
yDown = evt.touches[0].clientY;
};
function handleTouchMove(evt) {
if ( ! xDown || ! yDown ) {
return;
}
var xUp = evt.touches[0].clientX;
var yUp = evt.touches[0].clientY;
var xDiff = xDown - xUp;
var yDiff = yDown - yUp;
if(Math.abs( xDiff )+Math.abs( yDiff )>150){ //to deal with to short swipes
if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
if ( xDiff > 0 ) {/* left swipe */
alert('left!');
} else {/* right swipe */
alert('right!');
}
} else {
if ( yDiff > 0 ) {/* up swipe */
alert('Up!');
} else { /* down swipe */
alert('Down!');
}
}
/* reset values */
xDown = null;
yDown = null;
}
};
trashold, тайм-аут, swipeBlockElems add.
document.addEventListener('touchstart', handleTouchStart, false);
document.addEventListener('touchmove', handleTouchMove, false);
document.addEventListener('touchend', handleTouchEnd, false);
const SWIPE_BLOCK_ELEMS = [
'swipBlock',
'handle',
'drag-ruble'
]
let xDown = null;
let yDown = null;
let xDiff = null;
let yDiff = null;
let timeDown = null;
const TIME_TRASHOLD = 200;
const DIFF_TRASHOLD = 130;
function handleTouchEnd() {
let timeDiff = Date.now() - timeDown;
if (Math.abs(xDiff) > Math.abs(yDiff)) { /*most significant*/
if (Math.abs(xDiff) > DIFF_TRASHOLD && timeDiff < TIME_TRASHOLD) {
if (xDiff > 0) {
// console.log(xDiff, TIME_TRASHOLD, DIFF_TRASHOLD)
SWIPE_LEFT(LEFT) /* left swipe */
} else {
// console.log(xDiff)
SWIPE_RIGHT(RIGHT) /* right swipe */
}
} else {
console.log('swipeX trashhold')
}
} else {
if (Math.abs(yDiff) > DIFF_TRASHOLD && timeDiff < TIME_TRASHOLD) {
if (yDiff > 0) {
/* up swipe */
} else {
/* down swipe */
}
} else {
console.log('swipeY trashhold')
}
}
/* reset values */
xDown = null;
yDown = null;
timeDown = null;
}
function containsClassName (evntarget , classArr) {
for (var i = classArr.length - 1; i >= 0; i--) {
if( evntarget.classList.contains(classArr[i]) ) {
return true;
}
}
}
function handleTouchStart(evt) {
let touchStartTarget = evt.target;
if( containsClassName(touchStartTarget, SWIPE_BLOCK_ELEMS) ) {
return;
}
timeDown = Date.now()
xDown = evt.touches[0].clientX;
yDown = evt.touches[0].clientY;
xDiff = 0;
yDiff = 0;
}
function handleTouchMove(evt) {
if (!xDown || !yDown) {
return;
}
var xUp = evt.touches[0].clientX;
var yUp = evt.touches[0].clientY;
xDiff = xDown - xUp;
yDiff = yDown - yUp;
}
Если кто-то пытается использовать jQuery Mobile на Android и имеет проблемы с обнаружением прокрутки JQM
(у меня были некоторые на Xperia Z1, Galaxy S3, Nexus 4 и некоторые телефоны Wiko), это может быть полезно:
//Fix swipe gesture on android
if(android){ //Your own device detection here
$.event.special.swipe.verticalDistanceThreshold = 500
$.event.special.swipe.horizontalDistanceThreshold = 10
}
Прокрутка на андроиде не была обнаружена, если она не была очень длинной, точной и быстрой салфетки.
С этими двумя строками он работает правильно
У меня были проблемы с обработчиком touchhend, который стрелял непрерывно, когда пользователь тащил палец. Я не знаю, что из-за того, что я делаю неправильно или нет, но я переработал это, чтобы накапливать движения с помощью touchmove и прикосновение фактически срабатывает обратный вызов.
Мне также нужно было иметь большое количество этих экземпляров, поэтому я добавил методы включения/выключения.
И порог, когда короткий салфетка не срабатывает. Прикоснитесь к ноль счетчиков каждый раз.
Вы можете изменить target_node на лету. Включить при создании необязательно.
/** Usage: */
touchevent = new Modules.TouchEventClass(callback, target_node);
touchevent.enable();
touchevent.disable();
/**
*
* Touch event module
*
* @param method set_target_mode
* @param method __touchstart
* @param method __touchmove
* @param method __touchend
* @param method enable
* @param method disable
* @param function callback
* @param node target_node
*/
Modules.TouchEventClass = class {
constructor(callback, target_node, enable=false) {
/** callback function */
this.callback = callback;
this.xdown = null;
this.ydown = null;
this.enabled = false;
this.target_node = null;
/** move point counts [left, right, up, down] */
this.counts = [];
this.set_target_node(target_node);
/** Enable on creation */
if (enable === true) {
this.enable();
}
}
/**
* Set or reset target node
*
* @param string/node target_node
* @param string enable (optional)
*/
set_target_node(target_node, enable=false) {
/** check if we're resetting target_node */
if (this.target_node !== null) {
/** remove old listener */
this.disable();
}
/** Support string id of node */
if (target_node.nodeName === undefined) {
target_node = document.getElementById(target_node);
}
this.target_node = target_node;
if (enable === true) {
this.enable();
}
}
/** enable listener */
enable() {
this.enabled = true;
this.target_node.addEventListener("touchstart", this.__touchstart.bind(this));
this.target_node.addEventListener("touchmove", this.__touchmove.bind(this));
this.target_node.addEventListener("touchend", this.__touchend.bind(this));
}
/** disable listener */
disable() {
this.enabled = false;
this.target_node.removeEventListener("touchstart", this.__touchstart);
this.target_node.removeEventListener("touchmove", this.__touchmove);
this.target_node.removeEventListener("touchend", this.__touchend);
}
/** Touchstart */
__touchstart(event) {
event.stopPropagation();
this.xdown = event.touches[0].clientX;
this.ydown = event.touches[0].clientY;
/** reset count of moves in each direction, [left, right, up, down] */
this.counts = [0, 0, 0, 0];
}
/** Touchend */
__touchend(event) {
let max_moves = Math.max(...this.counts);
if (max_moves > 500) { // set this threshold appropriately
/** swipe happened */
let index = this.counts.indexOf(max_moves);
if (index == 0) {
this.callback("left");
} else if (index == 1) {
this.callback("right");
} else if (index == 2) {
this.callback("up");
} else {
this.callback("down");
}
}
}
/** Touchmove */
__touchmove(event) {
event.stopPropagation();
if (! this.xdown || ! this.ydown) {
return;
}
let xup = event.touches[0].clientX;
let yup = event.touches[0].clientY;
let xdiff = this.xdown - xup;
let ydiff = this.ydown - yup;
/** Check x or y has greater distance */
if (Math.abs(xdiff) > Math.abs(ydiff)) {
if (xdiff > 0) {
this.counts[0] += Math.abs(xdiff);
} else {
this.counts[1] += Math.abs(xdiff);
}
} else {
if (ydiff > 0) {
this.counts[2] += Math.abs(ydiff);
} else {
this.counts[3] += Math.abs(ydiff);
}
}
}
}
Используется два:
jQuery mobile: работают в большинстве случаев, и особенно когда вы разрабатываете приложение, которое использует другой плагин jQuery, тогда лучше использовать для этого jQuery для мобильных устройств. Посетите его здесь: https://www.w3schools.com/jquerymobile/jquerymobile_events_touch.asp
Hammer Time! - одна из лучших, легких и быстрых javascript-библиотек. Посетите его здесь: https://hammerjs.github.io/
Если вам просто нужно провести пальцем по экрану, вам лучше выбрать размер, используя только ту часть, которая вам нужна. Это должно работать на любом сенсорном устройстве.
Это ~ 450 байт после сжатия gzip, минификации, babel и т.д.
Я написал приведенный ниже класс на основе других ответов, он использует процентное смещение вместо пикселей и шаблон диспетчера событий, чтобы зацеплять/отцеплять вещи.
Используйте это так:
const dispatcher = new SwipeEventDispatcher(myElement);
dispatcher.on('SWIPE_RIGHT', () => { console.log('I swiped right!') })
export class SwipeEventDispatcher {
constructor(element, options = {}) {
this.evtMap = {
SWIPE_LEFT: [],
SWIPE_UP: [],
SWIPE_DOWN: [],
SWIPE_RIGHT: []
};
this.xDown = null;
this.yDown = null;
this.element = element;
this.options = Object.assign({ triggerPercent: 0.3 }, options);
element.addEventListener('touchstart', evt => this.handleTouchStart(evt), false);
element.addEventListener('touchend', evt => this.handleTouchEnd(evt), false);
}
on(evt, cb) {
this.evtMap[evt].push(cb);
}
off(evt, lcb) {
this.evtMap[evt] = this.evtMap[evt].filter(cb => cb !== lcb);
}
trigger(evt, data) {
this.evtMap[evt].map(handler => handler(data));
}
handleTouchStart(evt) {
this.xDown = evt.touches[0].clientX;
this.yDown = evt.touches[0].clientY;
}
handleTouchEnd(evt) {
const deltaX = evt.changedTouches[0].clientX - this.xDown;
const deltaY = evt.changedTouches[0].clientY - this.yDown;
const distMoved = Math.abs(Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : deltaY);
const activePct = distMoved / this.element.offsetWidth;
if (activePct > this.options.triggerPercent) {
if (Math.abs(deltaX) > Math.abs(deltaY)) {
deltaX < 0 ? this.trigger('SWIPE_LEFT') : this.trigger('SWIPE_RIGHT');
} else {
deltaY > 0 ? this.trigger('SWIPE_UP') : this.trigger('SWIPE_DOWN');
}
}
}
}
export default SwipeEventDispatcher;
Я также объединил несколько ответов, в основном первый и второй с классами, и вот моя версия:
export default class Swipe {
constructor(options) {
this.xDown = null;
this.yDown = null;
this.options = options;
this.handleTouchStart = this.handleTouchStart.bind(this);
this.handleTouchMove = this.handleTouchMove.bind(this);
document.addEventListener('touchstart', this.handleTouchStart, false);
document.addEventListener('touchmove', this.handleTouchMove, false);
}
onLeft() {
this.options.onLeft();
}
onRight() {
this.options.onRight();
}
onUp() {
this.options.onUp();
}
onDown() {
this.options.onDown();
}
static getTouches(evt) {
return evt.touches // browser API
}
handleTouchStart(evt) {
const firstTouch = Swipe.getTouches(evt)[0];
this.xDown = firstTouch.clientX;
this.yDown = firstTouch.clientY;
}
handleTouchMove(evt) {
if ( ! this.xDown || ! this.yDown ) {
return;
}
let xUp = evt.touches[0].clientX;
let yUp = evt.touches[0].clientY;
let xDiff = this.xDown - xUp;
let yDiff = this.yDown - yUp;
if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
if ( xDiff > 0 && this.options.onLeft) {
/* left swipe */
this.onLeft();
} else if (this.options.onRight) {
/* right swipe */
this.onRight();
}
} else {
if ( yDiff > 0 && this.options.onUp) {
/* up swipe */
this.onUp();
} else if (this.options.onDown){
/* down swipe */
this.onDown();
}
}
/* reset values */
this.xDown = null;
this.yDown = null;
}
}
После этого можете использовать его следующим образом:
let swiper = new Swipe({
onLeft() {
console.log('You swiped left.');
}
});
Это помогает избежать ошибок консоли, когда вы хотите вызывать только метод скажем "onLeft".
Пример использования со смещением.
// at least 100 px are a swipe
// you can use the value relative to screen size: window.innerWidth * .1
const offset = 100;
let xDown, yDown
window.addEventListener('touchstart', e => {
const firstTouch = getTouch(e);
xDown = firstTouch.clientX;
yDown = firstTouch.clientY;
});
window.addEventListener('touchend', e => {
if (!xDown || !yDown) {
return;
}
const {
clientX: xUp,
clientY: yUp
} = getTouch(e);
const xDiff = xDown - xUp;
const yDiff = yDown - yUp;
const xDiffAbs = Math.abs(xDown - xUp);
const yDiffAbs = Math.abs(yDown - yUp);
// at least <offset> are a swipe
if (Math.max(xDiffAbs, yDiffAbs) < offset ) {
return;
}
if (xDiffAbs > yDiffAbs) {
if ( xDiff > 0 ) {
console.log('left');
} else {
console.log('right');
}
} else {
if ( yDiff > 0 ) {
console.log('up');
} else {
console.log('down');
}
}
});
function getTouch (e) {
return e.changedTouches[0]
}
Простой ванильный пример JS для горизонтального смахивания:
let touchstartX = 0
let touchendX = 0
const slider = document.getElementById('slider')
function handleGesure() {
if (touchendX < touchstartX) alert('swiped left!')
if (touchendX > touchstartX) alert('swiped right!')
}
slider.addEventListener('touchstart', e => {
touchstartX = e.changedTouches[0].screenX
})
slider.addEventListener('touchend', e => {
touchendX = e.changedTouches[0].screenX
handleGesure()
})
Вы можете использовать практически ту же логику для вертикальной прокрутки.
Возможно, вам будет проще сначала реализовать его с помощью событий мыши для создания прототипа.
Здесь есть много ответов, включая верхние, поэтому их следует использовать с осторожностью, поскольку они не учитывают крайние случаи, особенно вокруг ограничивающих рамок.
См:
Вам нужно будет поэкспериментировать, чтобы поймать крайние случаи и поведение, например, указатель, перемещающийся за пределы элемента, прежде чем закончить.
Проведение - это очень простой жест, представляющий собой более высокий уровень обработки взаимодействия с указателем на интерфейсе, находящийся примерно между обработкой необработанных событий и распознаванием рукописного ввода.
Не существует единого точного метода обнаружения удара или броска, хотя практически все обычно следуют основному принципу обнаружения движения через элемент с порогом расстояния и скорости или скорости. Вы могли бы просто сказать, что если в течение заданного времени происходит перемещение на 65% размера экрана в заданном направлении, то это будет скачком. Где именно вы рисуете линию и как вы ее рассчитываете, зависит от вас.
Кто-то может также взглянуть на него с точки зрения импульса в направлении и как далеко от экрана он был сдвинут, когда элемент отпущен. Это становится понятнее при использовании липких движений, когда элемент можно перетаскивать, а затем при отпускании он либо отскакивает назад, либо отлетает с экрана, как если бы резинка сломалась.
Возможно, лучше всего попытаться найти библиотеку жестов, которую можно либо портировать, либо повторно использовать, которая обычно используется для согласованности. Многие из приведенных здесь примеров являются чрезмерно упрощенными, регистрируя движение как малейшее касание в любом направлении.
Android был бы очевидным выбором, хотя имеет противоположную проблему, он слишком сложный.
Похоже, что многие люди неверно истолковали этот вопрос как любое движение в направлении. Размах - это широкое и относительно короткое движение в подавляющем большинстве случаев в одном направлении (хотя оно может быть дугообразным и иметь определенные свойства ускорения). Бросок аналогичен, но намеревается случайно отбросить предмет на достаточное расстояние под действием собственного импульса.
Эти два достаточно похожи, что некоторые библиотеки могут обеспечить только бросок или пролистывание, которые могут использоваться взаимозаменяемо. На плоском экране трудно по-настоящему разделить два жеста, и вообще говоря, люди делают оба (смахивание физического экрана, но переключение элемента пользовательского интерфейса, отображаемого на экране).
Лучший вариант - не делать это самостоятельно. Уже есть большое количество библиотек JavaScript для обнаружения простых жестов.
Я хотел обнаруживать только левое и правое пролистывание, но вызывать действие только после окончания события касания, поэтому я слегка изменил ответ @givanse great для этого.
Зачем это делать? Если, например, во время считывания пользователь замечает, что он, наконец, не хочет его проводить, он может переместить палец в исходное положение (это делает очень популярное приложение для знакомств на телефоне;)), а затем событие "Проведите вправо" отменяется.
Поэтому, чтобы избежать события "проведите вправо" просто потому, что разница по горизонтали составляет 3 пикселя, я добавил пороговое значение, при котором событие отбрасывается: для того, чтобы событие "проходило вправо", пользователь должен провести по крайней мере 1/3 ширины браузера (конечно, вы можете изменить это).
Все эти мелкие детали улучшают пользовательский опыт. Вот код (Vanilla JS):
var xDown = null, yDown = null, xUp = null, yUp = null;
document.addEventListener('touchstart', touchstart, false);
document.addEventListener('touchmove', touchmove, false);
document.addEventListener('touchend', touchend, false);
function touchstart(evt) { const firstTouch = (evt.touches || evt.originalEvent.touches)[0]; xDown = firstTouch.clientX; yDown = firstTouch.clientY; }
function touchmove(evt) { if (!xDown || !yDown ) return; xUp = evt.touches[0].clientX; yUp = evt.touches[0].clientY; }
function touchend(evt) {
var xDiff = xUp - xDown, yDiff = yUp - yDown;
if ((Math.abs(xDiff) > Math.abs(yDiff)) && (Math.abs(xDiff) > 0.33 * document.body.clientWidth)) {
if (xDiff < 0)
document.getElementById('leftnav').click();
else
document.getElementById('rightnav').click();
}
xDown = null, yDown = null;
}