iPhone 및 Android의 JavaScript에서 손가락 스와이프 감지
사용자가 JavaScript를 사용하여 웹 페이지에서 손가락을 어떤 방향으로 움직인 것을 어떻게 감지할 수 있습니까?
iPhone과 Android phone의 양쪽 웹사이트에서 사용할 수 있는 하나의 솔루션이 있는지 궁금합니다.
단순 바닐라 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 ) {
/* right swipe */
} else {
/* left swipe */
}
} else {
if ( yDiff > 0 ) {
/* down swipe */
} else {
/* up swipe */
}
}
/* reset values */
xDown = null;
yDown = null;
};
Android에서 테스트 완료.
수평 스와이프를 위한 간단한 바닐라 JS 예제:
let touchstartX = 0
let touchendX = 0
function checkDirection() {
if (touchendX < touchstartX) alert('swiped left!')
if (touchendX > touchstartX) alert('swiped right!')
}
document.addEventListener('touchstart', e => {
touchstartX = e.changedTouches[0].screenX
})
document.addEventListener('touchend', e => {
touchendX = e.changedTouches[0].screenX
checkDirection()
})
수직 스와이프에도 같은 논리를 사용할 수 있습니다.
여기서는 몇 가지 답변을 CustomEvent를 사용하여 DOM에서 스위프된 이벤트를 실행하는 스크립트에 통합했습니다.페이지에 0.7k swiped-events.min.js 스크립트를 추가하고 swiped 이벤트를 듣습니다.
스와프
document.addEventListener('swiped', function(e) {
console.log(e.target); // the element that was swiped
console.log(e.detail.dir); // swiped direction
});
왼쪽을 스윕했다
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>
디폴트 어플리케이션 전체를 설정하려면 topmost 요소에 config 속성을 설정합니다.
<body data-swipe-threshold="100" data-swipe-timeout="250">
<div>Swipe me</div>
<div>or me</div>
</body>
소스 코드는 Github에서 사용할 수 있습니다.
@givanse의 답변에 따르면 다음과 같이 할 수 있습니다.
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();
@givanse brilliant 답변은 스와이프 액션을 등록하기 위한 여러 모바일 브라우저에서 가장 신뢰할 수 있고 호환성이 있다는 것을 알게 되었습니다.
다만, 그의 코드가 변경되어 현재 사용하고 있는 모바일브라우저에서 동작하고 있습니다.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;
};
테스트 대상:
- 안드로이드:Chrome, UC 브라우저
- iOS: Safari, Chrome, UC 브라우저
이전에 사용한 것은 마우스 다운 이벤트를 검출하고 x, y 위치를 기록한 다음 마우스 업 이벤트를 검출하고 두 값을 빼야 한다는 것입니다.
jQuery Mobile에는 스와이프 지원도 포함되어 있습니다.http://api.jquerymobile.com/swipe/
예
$("#divId").on("swipe", function(event) {
alert("It's a swipe!");
});
짧은 스윕에 대처하기 위한 다소 과장된 답변(코멘트...)
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;
}
};
짧은 jquery 플러그인으로 재패키지했습니다.
임계값, 시간 초과 스와이프, 스와이프 BlockElems 추가.
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_THRESHOLD = 200;
const DIFF_THRESHOLD = 130;
function handleTouchEnd() {
let timeDiff = Date.now() - timeDown;
if (Math.abs(xDiff) > Math.abs(yDiff)) { /*most significant*/
if (Math.abs(xDiff) > DIFF_THRESHOLD && timeDiff < TIME_THRESHOLD) {
if (xDiff > 0) {
// console.log(xDiff, TIME_THRESHOLD, DIFF_THRESHOLD)
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_THRESHOLD && timeDiff < TIME_THRESHOLD) {
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;
}
좌우 스와이프만 검출하고 싶었지만 터치 이벤트가 종료되었을 때만 액션을 개시하고 싶었기 때문에 @givanse의 훌륭한 답변을 약간 수정했습니다.
왜 그래야 하죠?예를 들어 스와이프 중에 사용자가 스와이프를 하고 싶지 않다는 것을 알게 되면 사용자가 손가락을 원래 위치에서 움직일 수 있습니다(매우 인기 있는 "데이트" 전화 어플리케이션에서 이렇게 합니다). 그러면 "오른쪽 스와이프" 이벤트가 취소됩니다.
따라서 가로로 3px 차이가 난다는 이유만으로 "오른쪽 스위프" 이벤트를 피하기 위해 이벤트가 폐기되는 임계값을 추가했습니다. "오른쪽 스위프" 이벤트를 수행하려면 사용자가 브라우저 폭의 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;
}
Android에서 jQuery Mobile을 사용하려고 하는데 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
}
안드로이드의 스와이프는 매우 길고 정확하며 빠른 스와이프가 아니면 감지되지 않습니다.
이 2개의 라인에서는 올바르게 동작합니다.
사용자가 손가락질하는 동안 터치핸들러가 계속 발사되는 데 문제가 있었습니다.내가 잘못했기 때문인지 아닌지 모르겠지만 터치모브에 의한 움직임 축적을 위해 다시 배선하여 실제로 콜백을 실행합니다.
또한 이러한 인스턴스가 다수 필요하기 때문에 활성화/비활성화 메서드를 추가했습니다.
그리고 문턱은 짧게 문지르면 발사되지 않는다.터치스타트 0은 매번 카운터입니다.
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);
}
}
}
}
여기에 이 답을 더합니다.이 기능은 데스크톱에서 테스트하기 위한 마우스 이벤트 지원을 추가합니다.
<!--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!') })
});
스와이프만 필요한 경우에는 필요한 부품만 사용하는 것이 좋습니다.이 조작은, 어느 터치 디바이스에서도 동작합니다.
이는 gzip 압축, 최소화, 바벨 등을 실행한 후 450바이트까지입니다.
아래 클래스는 다른 답변을 바탕으로 작성했습니다.픽셀 대신 이동 비율과 이벤트 디스패처 패턴을 사용하여 후크/언록합니다.
다음과 같이 사용합니다.
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.');
}
});
예를 들어 "왼쪽" 메서드만 호출하는 경우 콘솔 오류를 방지하는 데 도움이 됩니다.
2개 사용:
jQuery mobile: 대부분의 경우 동작합니다.특히 다른 jQuery 플러그인을 사용하는 응용 프로그램을 개발할 때는 이를 위해 jQuery 모바일 컨트롤을 사용하는 것이 좋습니다.여기를 봐주세요.https://www.w3schools.com/jquerymobile/jquerymobile_events_touch.asp
Hammer Time! 최고, 경량, 빠른 Javascript 기반 라이브러리 중 하나입니다.여기를 봐주세요.https://hammerjs.github.io/
리액트 훅으로 기능하도록 @givanse의 솔루션을 재작업했습니다.입력은 일부 옵션 이벤트 수신기이고 출력은 기능 참조입니다(ref가 변경될 때 후크가 다시 실행되도록 기능해야 함).
또한 작은 모션이 실수로 이벤트 청취자를 트리거하지 않도록 수직/수평 스와이프 임계값 파라미터에 추가되지만, 원래 답변을 보다 가깝게 모방하기 위해 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);
};
마우스 이벤트에서 프로토타입으로 처음 구현하는 것이 더 쉬울 수 있습니다.
여기에는 상단을 포함한 많은 답변이 있습니다.상자는 특히 경계 상자 주변의 가장자리 케이스를 고려하지 않기 때문에 주의해야 합니다.
참조:
종료하기 전에 포인터가 요소 밖으로 이동하는 것과 같은 가장자리 케이스와 동작을 포착하는 실험을 해야 합니다.
스와이프는 매우 기본적인 제스처이며 원시 이벤트 처리와 필기 인식 사이에 대략적으로 위치하여 인터페이스 포인터 상호 작용을 처리하는 상위 레벨의 동작입니다.
스와이프나 튕김을 검출하는 정확한 방법은 없지만, 거의 모든 것이 거리, 속도 또는 속도의 문턱값을 가진 요소를 가로지르는 움직임을 검출하는 기본 원리를 따릅니다.단순히 주어진 시간 내에 화면 크기의 65%를 특정 방향으로 이동하면 스와이프라고 말할 수 있습니다.정확히 어디에 선을 그리고 어떻게 계산하느냐는 당신에게 달려 있습니다.
또한 어떤 방향에서 운동량의 관점에서, 그리고 원소가 방출되었을 때 그것이 얼마나 화면 밖으로 밀려났는지 볼 수도 있다.이것은 요소를 끌어서 놓으면 다시 튀어오르거나 탄성이 깨진 것처럼 화면에서 튀어 나올 수 있는 스틱스위프를 사용하면 더 선명합니다.
일관성을 유지하기 위해 일반적으로 사용되는 포팅 또는 재사용할 수 있는 제스처 라이브러리를 찾는 것이 이상적일 수 있습니다.이 예제의 대부분은 지나치게 단순하며, 어느 방향으로든 가볍게 스와이프를 할 수 있습니다.
안드로이드는 분명 반대의 문제가 있지만 너무 복잡합니다.
많은 사람들이 그 질문을 어떤 방향으로의 움직임으로 잘못 해석한 것 같다.스와이프는 단방향으로 압도적으로 넓고 비교적 짧은 움직임입니다(호형일 수도 있고 특정 가속도 특성이 있습니다).썸은 그 자체의 기세 아래 어떤 물건을 무심코 멀리 밀어내려는 의도도 있지만 유사하다.
이 두 가지는 매우 유사하기 때문에 일부 라이브러리는 서로 바꿔 사용할 수 있는 플랭 또는 스와이프만 제공할 수 있습니다.평면 화면에서는 두 제스처를 제대로 구분하기가 어려우며 일반적으로 말하는 사람들은 두 가지 제스처를 모두 수행합니다(물리 화면은 바꾸되 화면에 표시된 UI 요소는 돌립니다).
스스로 하지 않는 것이 최선이다.간단한 제스처를 검출하기 위한 JavaScript 라이브러리는 이미 다수 존재합니다.
간격띄우기와 함께 사용하는 방법의 예제입니다.
// 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]
}
좌우 스와이프를 감지하기 위해 회전목마용 간단한 대본을 작성해야 했다.
Touch Events 대신 Pointer Events를 사용했습니다.
이것이 개인에게 도움이 되기를 바라며, 코드를 개선할 수 있는 어떠한 통찰도 환영합니다. 이 스레드에 매우 우수한 JS 개발자와 함께 참여하는 것은 다소 부담스럽습니다.
function getSwipeX({elementId}) {
this.e = document.getElementsByClassName(elementId)[0];
this.initialPosition = 0;
this.lastPosition = 0;
this.threshold = 200;
this.diffInPosition = null;
this.diffVsThreshold = null;
this.gestureState = 0;
this.getTouchStart = (event) => {
event.preventDefault();
if (window.PointerEvent) {
this.e.setPointerCapture(event.pointerId);
}
return this.initalTouchPos = this.getGesturePoint(event);
}
this.getTouchMove = (event) => {
event.preventDefault();
return this.lastPosition = this.getGesturePoint(event);
}
this.getTouchEnd = (event) => {
event.preventDefault();
if (window.PointerEvent) {
this.e.releasePointerCapture(event.pointerId);
}
this.doSomething();
this.initialPosition = 0;
}
this.getGesturePoint = (event) => {
this.point = event.pageX
return this.point;
}
this.whatGestureDirection = (event) => {
this.diffInPosition = this.initalTouchPos - this.lastPosition;
this.diffVsThreshold = Math.abs(this.diffInPosition) > this.threshold;
(Math.sign(this.diffInPosition) > 0) ? this.gestureState = 'L' : (Math.sign(this.diffInPosition) < 0) ? this.gestureState = 'R' : this.gestureState = 'N';
return [this.diffInPosition, this.diffVsThreshold, this.gestureState];
}
this.doSomething = (event) => {
let [gestureDelta,gestureThreshold,gestureDirection] = this.whatGestureDirection();
// USE THIS TO DEBUG
console.log(gestureDelta,gestureThreshold,gestureDirection);
if (gestureThreshold) {
(gestureDirection == 'L') ? // LEFT ACTION : // RIGHT ACTION
}
}
if (window.PointerEvent) {
this.e.addEventListener('pointerdown', this.getTouchStart, true);
this.e.addEventListener('pointermove', this.getTouchMove, true);
this.e.addEventListener('pointerup', this.getTouchEnd, true);
this.e.addEventListener('pointercancel', this.getTouchEnd, true);
}
}
new를 사용하여 함수를 호출할 수 있습니다.
window.addEventListener('load', () => {
let test = new getSwipeX({
elementId: 'your_div_here'
});
})
커스텀 리액트 훅을 사용하여 스와이프 이벤트를 처리하기 위한 @givanse의 놀라운 솔루션을 사용하기 위한 @ruben-martinez answer를 재작성했습니다.
import React, { useEffect, useRef, useState } from "react";
export default function useSwiper() {
const [domRef, setDomRef] = useState<any>();
const xDown: React.MutableRefObject<number | null> = useRef(null);
const yDown: React.MutableRefObject<number | null> = useRef(null);
useEffect(() => {
if (!domRef) return;
function getTouches(event: React.TouchEvent<HTMLDivElement>) {
return event.touches;
}
function handleTouchStart(event: any) {
const firstTouch = getTouches(event)[0];
xDown.current = firstTouch.clientX;
yDown.current = firstTouch.clientY;
}
function handleTouchMove(event: React.TouchEvent<HTMLDivElement>) {
if (!xDown.current || !yDown.current) return;
const firstTouch = getTouches(event)[0];
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)) {
// handle horizontal swipes
if (xDiff > 0) {
// we swiped right
console.log("right");
} else {
// we swiped left
console.log("left");
}
} else {
// handle vertical swipes
if (yDiff > 0) {
// we swiped down
console.log("down");
} else {
// we swiped up
console.log("up");
}
}
}
function handleTouchEnd(event: React.TouchEvent<HTMLDivElement>) {
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, false);
domRef.removeEventListener("touchmove", handleTouchMove, false);
domRef.removeEventListener("touchend", handleTouchEnd, false);
};
}, [domRef]);
return (ref: any) => setDomRef(ref);
}
그의 답변을 구현할 때 가장 큰 어려움은 스와이프 요소의 참조를 커스텀 훅에서 참조로 바인드하는 방법을 모른다는 것이었습니다.
기본적으로 커스텀 훅에서 기능을 되돌리는 것입니다.이 기능을 사용하면 듣고 싶은 요소의 참조를 전달하여 스와이프 작업을 수행할 수 있습니다.참조를 수신하면 커스텀 훅이 요소의 참조로 훅 상태를 업데이트하여 re 렌더링을 트리거하므로 실제 요소가 생성됩니다.
이 기능 참조 스타일은 여러 요소에 후크를 사용할 수도 있습니다.아래 그림과 같이 스와이프를 사용하여 삭제할 수 있는 아이템 목록으로 하고 싶었습니다.
import useSwiper from "./hooks/useSwipe";
const EntryCard = ({ entry, godMode, reload }: EntryProps) => {
const swiperRef = useSwiper();
const handleEntryClick =
(entry: Entry) => async (event: React.MouseEvent<HTMLDivElement>) => {
if (!godMode) return;
try {
reload((state) => !state);
} catch (err) {
console.log("Error deleting entry: ", err);
}
};
return (
<div className="item" onClick={handleEntryClick(entry)} ref={swiperRef}>
<div className="username">{entry.userName}</div>
<div className="score">{entry.weekScore}</div>
</div>
);
};
PS: 후크에 함수를 전달하여 스와이프 값을 받을 수 있습니다.감사합니다:) 마음에 드시면 투표해주세요:)
touchStart 및 touchEnd로 처리합니다.
var handleSwipe = function(elem,callbackOnRight, callbackOnLeft, callbackOnDown,
callbackOnUp) => {
elem.ontouchstart = handleTouchStart;
elem.ontouchend = handleTouchEnd;
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 handleTouchEnd(evt) {
if (!xDown || !yDown) {
return;
}
var xUp = evt.changedTouches[0].clientX;
var yUp = evt.changedTouches[0].clientY;
var xDiff = xDown - xUp;
var yDiff = yDown - yUp;
var minDif = 30;
console.log(`xDiff:${xDiff}, yDiff:${yDiff}`);
if (Math.abs(xDiff) > Math.abs(yDiff)) {
if (xDiff > minDif) {
if (callbackOnLeft)
callbackOnLeft();
} else if (xDiff < -1 * minDif){
if (callbackOnRight)
callbackOnRight();
}
} else {
if (yDiff > minDif) {
if (callbackOnDown)
callbackOnDown();
} else if (yDiff < -1* minDif){
if (callbackOnUp)
callbackOnUp();
}
}
xDown = null;
yDown = null;
};
}
노래는 들을 수 요.touchstart
★★★★★★★★★★★★★★★★★」touchend
이벤트 및 이벤트 데이터를 기반으로 방향과 힘을 계산합니다(코드펜).
let start = null;
document.addEventListener('touchstart', e => {
const touch = e.changedTouches[0];
start = [touch.clientX, touch.clientY];
});
document.addEventListener('touchend', e => {
const touch = e.changedTouches[0];
const end = [touch.clientX, touch.clientY];
document.body.innerText = `${end[0] - start[0]},${end[1] - start[1]}`;
});
Swipe here
또는 다음과 같은 동일한 개념(Codepen)을 중심으로 보다 인체공학적인 API를 구축할 수 있습니다.
const removeListener = addSwipeRightListener(document, (force, e) => {
console.info('Swiped right with force: ' + force);
});
// removeListener()
// swipe.js
const {
addSwipeLeftListener,
addSwipeRightListener,
addSwipeUpListener,
addSwipeDownListener,
} = (function() {
// <element, {listeners: [...], handleTouchstart, handleTouchend}>
const elements = new WeakMap();
function readTouch(e) {
const touch = e.changedTouches[0];
if (touch == undefined) {
return null;
}
return [touch.clientX, touch.clientY];
}
function addListener(element, cb) {
let elementValues = elements.get(element);
if (elementValues === undefined) {
const listeners = new Set();
const handleTouchstart = e => {
elementValues.start = readTouch(e);
};
const handleTouchend = e => {
const start = elementValues.start;
if (start === null) {
return;
}
const end = readTouch(e);
for (const listener of listeners) {
listener([end[0] - start[0], end[1] - start[1]], e);
}
};
element.addEventListener('touchstart', handleTouchstart);
element.addEventListener('touchend', handleTouchend);
elementValues = {
start: null,
listeners,
handleTouchstart,
handleTouchend,
};
elements.set(element, elementValues);
}
elementValues.listeners.add(cb);
return () => deleteListener(element, cb);
}
function deleteListener(element, cb) {
const elementValues = elements.get(element);
const listeners = elementValues.listeners;
listeners.delete(cb);
if (listeners.size === 0) {
elements.delete(element);
element.removeEventListener('touchstart', elementValues.handleTouchstart);
element.removeEventListener('touchend', elementValues.handleTouchend);
}
}
function addSwipeLeftListener(element, cb) {
return addListener(element, (force, e) => {
const [x, y] = force;
if (x < 0 && -x > Math.abs(y)) {
cb(x, e);
}
});
}
function addSwipeRightListener(element, cb) {
return addListener(element, (force, e) => {
const [x, y] = force;
if (x > 0 && x > Math.abs(y)) {
cb(x, e);
}
});
}
function addSwipeUpListener(element, cb) {
return addListener(element, (force, e) => {
const [x, y] = force;
if (y < 0 && -y > Math.abs(x)) {
cb(x, e);
}
});
}
function addSwipeDownListener(element, cb) {
return addListener(element, (force, e) => {
const [x, y] = force;
if (y > 0 && y > Math.abs(x)) {
cb(x, e);
}
});
}
return {
addSwipeLeftListener,
addSwipeRightListener,
addSwipeUpListener,
addSwipeDownListener,
}
})();
// app.js
function print(direction, force) {
document.querySelector('#direction').innerText = direction;
document.querySelector('#data').innerText = force;
}
addSwipeLeftListener(document, (force, e) => {
print('left', force);
});
addSwipeRightListener(document, (force, e) => {
print('right', force);
});
addSwipeUpListener(document, (force, e) => {
print('up', force);
});
addSwipeDownListener(document, (force, e) => {
print('down', force);
});
<h1>Swipe <span id="direction"></span></h1>
Force (px): <span id="data"></span>
언급URL : https://stackoverflow.com/questions/2264072/detect-a-finger-swipe-through-javascript-on-the-iphone-and-android
'sourcecode' 카테고리의 다른 글
sys.stdout 사용방법flush() 메서드 (0) | 2022.09.18 |
---|---|
자바에는 '조건이 true가 될 때까지 차단' 함수가 있습니까? (0) | 2022.09.18 |
'크기 조정' 이벤트가 끝날 때까지 기다렸다가 조치를 취하는 방법은 무엇입니까? (0) | 2022.09.17 |
Python의 빌트인 사전은 어떻게 구현됩니까? (0) | 2022.09.17 |
MariaDB 10.1 암호화 확인 (0) | 2022.09.17 |