Обнаружение пальца по JavaScript на iPhone и Android

268

Как вы можете обнаружить, что пользователь провел пальцем в каком-то направлении по веб-странице с помощью JavaScript?

Мне было интересно, есть ли одно решение, которое будет работать для веб-сайтов на iPhone и Android-телефон.

+827
источник
1
Для распознавания пролистывания я бы рекомендовал Hammer.js . Он довольно маленький и поддерживает множество жестов: - Проведите - Поверните - Сожмите - Нажмите (длительное удержание) - Коснитесь - Панорамируйте
Уилл Брикнер
Происходит событие: «touchmove»
глина
@ Глина, которая все еще не работает в Safari, так что нет iPhone.
Jakuje

Ответы:

342

Простой ванильный пример кода 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.

givanse
источник
1
Выглядит круто и просто, любую идею , что является поддержка этих событий touchstart, touchmove?
д.раев
1
Это работает довольно хорошо, но есть проблема с обнаружением прямых движений. Я опубликую другой ответ в этой теме, который исправил это как решение JQuery (десктоп). Он также добавляет версию этих событий смахивания мышью и добавляет параметр чувствительности.
Codebeat
1
Черт. Тема закрыта, поэтому не могу добавить мой ответ!
Codebeat
3
Это прекрасно работает, но влево / вправо и вверх / вниз назад.
Питер Айзентраут
4
originalEvent является свойством JQuery. Это следует исключить, если вы запускаете чистый JavaScript без JQuery. Текущий код вызывает исключение, если выполняется без JQuery.
Ян Дерк
31

Основываясь на ответе @ 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();
Marwelln
источник
7
этот код, вероятно, не будет работать, потому что вы получите исключение при попытке вызвать .bindundefined, потому что вы handleTouchMoveфактически ничего не возвращали. также бесполезно вызывать связывание при вызове функции, this.потому что оно уже связано с текущим контекстом
nick.skriabin
3
Я только что удалил, .bind(this);и это сработало изящно. спасибо @nicholas_r
Али Ганаватян
Часть получить элемент самостоятельно. Я просто удаляю '#' в document.getElementById ('my-element'), и это сработало хорошо. Спасибо @Marwelln :)
Синий трамвай
4
Если вы хотите дождаться окончания ENDS (то есть после того, как они подняли палец или onmouseup), измените touches[0]на changedTouches[0]и тип обработчика события handleTouchMoveнаhandleTouchEnd
TetraDev
позвоните run()дважды, и вы получите неприятную утечку памяти
Мат
20

Я объединил несколько ответов здесь в сценарий, который использует CustomEvent для запуска swiped- событий в 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

Джон Доэрти
источник
Я пришел сюда, потому что чистая прокрутка не работала на меня на МОБИЛЬНОМ
StefanBob
@StefanBob, если вы поставите галочку на репозитории github с достаточным количеством информации, чтобы позволить мне воспроизвести проблему, я рассмотрю ее
Джон Доэрти
1
Спасибо, работает отлично! Я заменил Hammer.js вашей библиотекой, потому что первая не работает с масштабированием браузера, и это серьезная проблема с удобством использования. С этой библиотекой зум работает правильно (протестировано на Android)
collimarco
15

То, что я использовал ранее, это то, что вы должны обнаружить событие mousedown, записать его x, y местоположение (в зависимости от того, что имеет отношение), затем обнаружить событие mouseup и вычесть два значения.

helloandre
источник
28
Я верю, что с touchstart, touchmove, touchcancel и touchend можно работать, а не mousedown или mouseup.
Volomike
15

jQuery Mobile также включает поддержку смахивания: http://api.jquerymobile.com/swipe/

пример

$("#divId").on("swipe", function(event) {
    alert("It's a swipe!");
});
Гринн
источник
4
Если кто-то не хочет, чтобы jQuery mobile манипулировал пользовательским интерфейсом, см .: stackoverflow.com/questions/8648596/…
Никлас Экман
12

Я обнаружил, что @givanse brilliant answer является наиболее надежным и совместимым с несколькими мобильными браузерами для регистрации действий смахивания.

Однако в его коде есть изменения, необходимые для его работы в современных мобильных браузерах 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;                                             
};

Проверено на:

  • Android : Chrome, браузер UC
  • iOS : Safari, Chrome, UC Browser
nashcheez
источник
originalEvent является свойством JQuery. Он даже не существует в чистом Javascript.
Ян Дерк
1
Согласно этому SO ответу , сенсорное событие, если поддерживается браузером, будет выставлено через event.originalEvent. Дело в том event.touches, прекратило свое существование в настоящее время и приводит undefined.
нащиз
event.touches перестал существовать только при использовании JQuery. Попробуйте свой код без JQuery, и вы получите ошибку, что evt.originalEvent не определен. JQuery полностью заменяет событие своим собственным и помещает родное событие браузера в originalevent. Краткая версия: Ваш код работает только с JQuery. Это работает без JQuery, если вы удалите originalevent.
Ян Дерк
1
Да, я немного исследовал и понял, что вы были правы насчет доступности jquery event.originalEvent. Я обновлю свой ответ. Спасибо! :)
нащиз
6

Какой-то мод самого откровенного ответа (не могу комментировать ...) для коротких ударов

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;
    }
};
rmnsh
источник
6

trashold, timeout swipe, swipeBlockElems добавить.

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;
}
Сергей Ганз
источник
4

Если кто-то пытается использовать 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
    }

Удар по Android не был обнаружен, если это не было очень длинным, точным и быстрым проведением.

С этими двумя строчками все работает правильно

Rayjax
источник
Мне также нужно было добавить: $.event.special.swipe.scrollSupressionThreshold = 8;но вы направили меня в правильном направлении! Спасибо!
Филипп Г,
4

У меня были проблемы с непрерывной стрельбой обработчика касаний, пока пользователь тащил палец. Я не знаю, связано ли это с чем-то, что я делаю неправильно или нет, но я переписал это, чтобы накапливать движения с помощью touchmove, и touchend фактически запускает обратный вызов.

Мне также нужно было иметь большое количество этих экземпляров, поэтому я добавил методы включения / выключения.

И порог, когда короткий удар не срабатывает. Сенсорный запуск нулевых счетчиков каждый раз.

Вы можете изменить 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);
            }
        }
    }
}
Trendal Toews
источник
Это для ES5 или ES6?
1,21 гигаватт
@gigawatts я не помню. Проект, который использовал, уже достиг EOL, и с тех пор я не нуждался в коде. Я подозреваю, что в то время я писал для ES6, но это было более 2 лет назад.
Trendal Toews
3

Я также объединил несколько ответов, в основном первый и второй с классами, и вот моя версия:

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".

Романна Семенышин
источник
2

Использовано два:

jQuery для мобильных устройств: работает в большинстве случаев, особенно когда вы разрабатываете приложение, которое использует другой плагин jQuery, тогда лучше использовать для этого мобильные элементы управления jQuery. Посетите его здесь: https://www.w3schools.com/jquerymobile/jquerymobile_events_touch.asp

Время Молота! одна из лучших, легких и быстрых библиотек на основе JavaScript. Посетите его здесь: https://hammerjs.github.io/

звезда
источник
2

Если вам просто нужно провести, вам лучше выбрать размер, используя только ту часть, которая вам нужна. Это должно работать на любом сенсорном устройстве.

Это ~ 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;

Vargr
источник
2

Я хотел , чтобы обнаружить левый и правый красть только, но вызвать действие только тогда , когда касание событий конца , так что я немного измененным большой ответ на @ givanse, чтобы сделать это.

Зачем это делать? Например, если во время пролистывания пользователь замечает, что, наконец, не хочет сильно ударить, он может переместить палец в исходное положение. (это делает очень популярное приложение для знакомств на телефоне;)), а затем «пролистать вправо». Событие отменено.

Таким образом, чтобы избежать события «проведите вправо» только из-за разницы в 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;
}
Basj
источник
1

Простой ванильный пример 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()
})

Вы можете использовать почти такую ​​же логику для вертикальной прокрутки.

Дамжан Павлица
источник
1

Добавление к этому ответу здесь . Этот добавляет поддержку событий мыши для тестирования на рабочем столе:

<!--scripts-->
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.isMouseDown = false;
        this.listenForMouseEvents = true;
        this.options = Object.assign({ triggerPercent: 0.3 }, options);

        element.addEventListener('touchstart', evt => this.handleTouchStart(evt), false);
        element.addEventListener('touchend', evt => this.handleTouchEnd(evt), false);
        element.addEventListener('mousedown', evt => this.handleMouseDown(evt), false);
        element.addEventListener('mouseup', evt => this.handleMouseUp(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;
    }

    handleMouseDown(evt) {
        if (this.listenForMouseEvents==false) return;
        this.xDown = evt.clientX;
        this.yDown = evt.clientY;
        this.isMouseDown = true;
    }

    handleMouseUp(evt) {
        if (this.isMouseDown == false) return;
        const deltaX = evt.clientX - this.xDown;
        const deltaY = evt.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');
            }
        }
    }

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

// add a listener on load
window.addEventListener("load", function(event) {
    const dispatcher = new SwipeEventDispatcher(document.body);
    dispatcher.on('SWIPE_RIGHT', () => { console.log('I swiped right!') })
    dispatcher.on('SWIPE_LEFT', () => { console.log('I swiped left!') })
});
1,21 гигаватт
источник
1

Я переработал решение @givanse , чтобы оно функционировало как React Hook. Input - это некоторые необязательные прослушиватели событий, output - это функциональная ссылка (должна быть функциональной, чтобы хук мог перезапуститься, когда / если ссылка изменилась).

Также добавлено в параметре порог вертикальной / горизонтальной прокрутки, чтобы небольшие движения не вызывали случайное прослушивание событий, но их можно установить на 0, чтобы более точно имитировать исходный ответ.

Совет: для лучшей производительности функции ввода прослушивателя событий должны быть запомнены.

function useSwipeDetector({
    // Event listeners.
    onLeftSwipe,
    onRightSwipe,
    onUpSwipe,
    onDownSwipe,

    // Threshold to detect swipe.
    verticalSwipeThreshold = 50,
    horizontalSwipeThreshold = 30,
}) {
    const [domRef, setDomRef] = useState(null);
    const xDown = useRef(null);
    const yDown = useRef(null);

    useEffect(() => {
        if (!domRef) {
            return;
        }

        function handleTouchStart(evt) {
            const [firstTouch] = evt.touches;
            xDown.current = firstTouch.clientX;
            yDown.current = firstTouch.clientY;
        };

        function handleTouchMove(evt) {
            if (!xDown.current || !yDown.current) {
                return;
            }

            const [firstTouch] = evt.touches;
            const xUp = firstTouch.clientX;
            const yUp = firstTouch.clientY;
            const xDiff = xDown.current - xUp;
            const yDiff = yDown.current - yUp;

            if (Math.abs(xDiff) > Math.abs(yDiff)) {/*most significant*/
                if (xDiff > horizontalSwipeThreshold) {
                    if (onRightSwipe) onRightSwipe();
                } else if (xDiff < -horizontalSwipeThreshold) {
                    if (onLeftSwipe) onLeftSwipe();
                }
            } else {
                if (yDiff > verticalSwipeThreshold) {
                    if (onUpSwipe) onUpSwipe();
                } else if (yDiff < -verticalSwipeThreshold) {
                    if (onDownSwipe) onDownSwipe();
                }
            }
        };

        function handleTouchEnd() {
            xDown.current = null;
            yDown.current = null;
        }

        domRef.addEventListener("touchstart", handleTouchStart, false);
        domRef.addEventListener("touchmove", handleTouchMove, false);
        domRef.addEventListener("touchend", handleTouchEnd, false);

        return () => {
            domRef.removeEventListener("touchstart", handleTouchStart);
            domRef.removeEventListener("touchmove", handleTouchMove);
            domRef.removeEventListener("touchend", handleTouchEnd);
        };
    }, [domRef, onLeftSwipe, onRightSwipe, onUpSwipe, onDownSwipe, verticalSwipeThreshold, horizontalSwipeThreshold]);

    return (ref) => setDomRef(ref);
};
Рубен Мартинес младший
источник
0

Пример использования со смещением.

// 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]
}

Илья Зеленько
источник
В настоящее время использую эту версию. Как я мог бы предотвратить это от многократного запуска, если сильно ударить в повторе? Я использую это с функцией анимации для формы с боковой прокруткой, и когда я провожу несколько раз, все становится немного странно, и мои div начинают перекрываться в видимой области.
NMALM
0

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

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

Видеть:

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

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

Не существует единого точного метода обнаружения удара или броска, хотя практически все обычно следуют базовому принципу обнаружения движения через элемент с порогом расстояния и скорости или скорости. Вы могли бы просто сказать, что если в течение заданного времени происходит перемещение на 65% размера экрана в заданном направлении, то это будет скачком. Где именно вы рисуете линию и как вы ее рассчитываете, зависит от вас.

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

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

Android был бы очевидным выбором, хотя имеет противоположную проблему, он слишком сложен.

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

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

Лучший вариант - не делать это самостоятельно. Уже есть большое количество библиотек JavaScript для обнаружения простых жестов .

jgmjgm
источник