提问人:827 提问时间:2/15/2010 更新时间:6/18/2023 访问量:449104
在 iPhone 和 Android 上检测手指轻扫 JavaScript
Detect a finger swipe through JavaScript on the iPhone and Android
问:
如何检测用户在使用 JavaScript 的网页上向某个方向滑动手指?
我想知道是否有一种解决方案适用于 iPhone 和 Android 手机上的网站。
答:
我以前使用的是,您必须检测 MouseDown 事件,记录其 x,y 位置(以相关为准),然后检测 mouseup 事件,并减去这两个值。
评论
jQuery Mobile 还包括滑动支持: http://api.jquerymobile.com/swipe/
例
$("#divId").on("swipe", function(event) {
alert("It's a swipe!");
});
评论
我已将 TouchWipe
重新打包为一个简短的 jquery 插件:detectSwipe
简单的 vanilla 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 中测试。
评论
touchstart
touchmove
如果有人尝试在 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
}
在 android 上滑动不会被检测到,除非它是非常长、精确和快速的滑动。
使用这两条线,它可以正常工作
评论
$.event.special.swipe.scrollSupressionThreshold = 8;
一些 uppest 答案的模组(不能评论......
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;
}
};
评论
if ( ! xDown || ! yDown || e.touches.length === 2 ) {
根据 @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();
评论
.bind
handleTouchMove
this.
.bind(this);
touches[0]
changedTouches[0]
handleTouchMove
handleTouchEnd
run()
阈值、超时滑动、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_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绝妙的答案是用于注册滑动操作的多个移动浏览器中最可靠和最兼容的。
但是,他的代码需要进行更改才能使其在使用 .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 浏览器
评论
event.originalEvent
event.touches
undefined
event.originalEvent
当用户拖动手指时,触摸端处理程序连续触发时,我遇到了麻烦。我不知道这是否是由于我做错了什么,但我重新连接了它以使用 touchmove 累积动作,touchend 实际上触发了回调。
我还需要有大量这样的实例,所以我添加了启用/禁用方法。
以及短划不触发的阈值。每次 Touchstart 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);
}
}
}
}
评论
使用两个:
jQuery mobile:在大多数情况下都可以工作,特别是当您开发使用其他jQuery插件的应用程序时,最好使用jQuery移动控件。在这里访问它: https://www.w3schools.com/jquerymobile/jquerymobile_events_touch.asp
锤子时间!最好、轻量级和快速的基于 JavaScript 的库之一。在这里访问它: https://hammerjs.github.io/
我将这里的一些答案合并到一个脚本中,该脚本使用 CustomEvent 在 DOM 中触发滑动事件。 将 0.7k swiped-events.min.js 脚本添加到页面并侦听滑动事件:
偷
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>
若要在应用程序范围内设置默认值,请在最顶层元素上设置配置属性:
<body data-swipe-threshold="100" data-swipe-timeout="250">
<div>Swipe me</div>
<div>or me</div>
</body>
源代码可在 Github 上找到
评论
如果您只需要滑动,最好只使用您需要的部分。 这应该适用于任何触摸设备。
这是 ~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;
如何与 offset 一起使用的示例。
// 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]
}
评论
我也合并了一些答案,主要是第一个答案和第二个答案,这是我的版本:
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”方法时,它有助于避免控制台错误。
水平滑动的简单 vanilla 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()
})
您可以对垂直滑动使用完全相同的逻辑。
评论
首先使用鼠标事件实现原型可能会更容易。
这里有很多答案,包括顶部,应该谨慎使用,因为它们不考虑边缘情况,尤其是在边界框周围。
看:
您将需要进行实验以捕获边缘情况和行为,例如指针在结束之前移动到元素之外。
滑动是一种非常基本的手势,它是一种更高级别的界面指针交互处理,大致介于处理原始事件和手写识别之间。
没有单一的精确方法来检测滑动或甩动,尽管几乎所有方法通常都遵循一个基本原理,即检测具有距离和速度或速度阈值的元素的运动。您可以简单地说,如果在给定时间内沿给定方向移动 65% 的屏幕尺寸,那么它就是滑动。你在哪里画线以及如何计算它取决于你。
有些人也可能从某个方向的动量以及元素释放时它被推离屏幕多远的角度来看待它。使用粘性滑动可以拖动元素,然后在释放时会反弹或飞出屏幕,就像松紧带断裂一样,这一点会更清晰。
尝试找到一个可以移植或重用的手势库可能是理想的选择,该手势库通常用于一致性。这里的许多示例都过于简单化,将滑动记录为在任何方向上最轻微的触摸。
Android将是显而易见的选择,尽管存在相反的问题,它过于复杂。
许多人似乎将这个问题误解为朝着某个方向的任何运动。滑动是在一个方向上压倒性地进行的广泛且相对短暂的运动(尽管可能是弧形的并具有一定的加速特性)。甩球与此类似,但目的是在自身动量下随意地将物品推开一段距离。
这两者非常相似,以至于某些库可能只提供 fling 或 swipe,它们可以互换使用。在平面屏幕上,很难真正区分这两种手势,一般来说,人们会同时执行这两种操作(滑动物理屏幕,但甩动屏幕上显示的 UI 元素)。
你最好的选择是不要自己动手。已经有大量的 JavaScript 库用于检测简单的手势。
我只想检测左右滑动,但只在触摸事件结束时触发操作,所以我稍微修改了@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;
}
在这里补充这个答案。这增加了对鼠标事件的支持,以便在桌面上进行测试:
<!--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!') })
});
评论
我重新设计了 @givanse 的解决方案,使其充当 React 钩子。输入是一些可选的事件侦听器,输出是功能性 ref(需要功能性,以便钩子可以在 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);
};
评论
我必须为轮播编写一个简单的脚本来检测向左或向右滑动。
我使用了指针事件而不是触摸事件。
我希望这对个人有用,我欢迎任何改进我的代码的见解;与非常优秀的 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'
});
})
我重新设计了@ruben-martinez的答案,使用@givanse的惊人解决方案来使用自定义react钩子处理滑动事件。
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);
}
我在实现他的答案时面临的主要挑战是不知道如何将滑动元素的 ref 绑定到自定义钩子中的 ref。
基本上,我们从自定义钩子返回一个函数。这个函数将允许我们从我们想要侦听的元素中传入一个引用,以滑动操作。然后,收到 ref 的自定义钩子会使用元素的 ref 更新钩子状态,这会触发重新渲染,因此我们有了实际的元素!
这种函数式 ref 样式还允许我们将钩子用于多个元素。如下图所示,我想将它用于项目列表,以启用滑动以删除:)
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;
};
}
您可以监听事件,并根据事件数据(Codepen)计算方向和力: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>
除了这里建议的内容之外,我会跟踪手指数字,因为如果你同时用两根手指触摸,它会在没有 ~swipe~ 运动的情况下拾取 X 位置,这会导致奇怪的行为,而且,你可能想要设置一个“距离”最小值,这样用户就不会在触摸您的网站或应用程序时错误地触发滑动。
//Swipe
let touchstartX = 0
let touchendX = 0
let fingerCount = 0
const checkDirection = () => {
const distance = 50 //Minimum distance for the swipe to work
//left
if (touchendX < touchstartX && (touchstartX - touchendX) > distance ){
//Do something cool
}
//right
if (touchendX > touchstartX && (touchendX - touchstartX) > distance){
//Do something cooler
}
document.addEventListener('touchstart', e => {
fingerCount = e.touches.length
touchstartX = e.changedTouches[0].clientX
})
document.addEventListener('touchend', e => {
touchendX = e.changedTouches[0].clientX
if(fingerCount === 1){
checkDirection()
}
})
功能检查水平和垂直方向,以确定哪个滑动时间更长,以防止执行 2 条指令,因为不可能进行完美的单向滑动。滑动总是在 X 和 Y 上有偏差。
let touchstartX = 0;
let touchendX = 0;
let touchstartY = 0;
let touchendY = 0;
function checkDirection() {
let difX = touchstartX - touchendX;
let difY = touchstartY - touchendY;
if (Math.abs(difX) > Math.abs(difY)) {
if (touchendX < touchstartX) {/*left*/}
if (touchendX > touchstartX) {/*right*/}
} else {
if (touchendY < touchstartY) {/*up*/}
if (touchendY > touchstartY) {/*down*/}
}
};
document.addEventListener('touchstart', e => {
e.preventDefault();
touchstartX = e.changedTouches[0].screenX;
touchstartY = e.changedTouches[0].screenY;
});
document.addEventListener('touchend', e => {
e.preventDefault();
touchendX = e.changedTouches[0].screenX;
touchendY = e.changedTouches[0].screenY;
checkDirection();
});
这是。理想情况下,如果滑动不够重要,您希望跳过滑动操作。JQuery
$('.slider')
.off('touchstart touchend swipedaction')
.on('touchstart', function (e) {
//Set the starting point directly on self
$(this).touchstartX = e.changedTouches[0].screenX;
})
.on('touchend', function (e) {
//Set the end point directly on self
let $self = $(this);
$self.touchendX = e.changedTouches[0].screenX;
// Swipe more than 50px, else don't action it.
if (Math.abs($self.touchendX - $self.touchstartX) > 50) {
if ($self.touchendX < $self.touchstartX) {
$self.trigger('swipedaction', ['left']);
} else {
$self.trigger('swipedaction', ['right']);
}
} else {
e.stopPropagation();
}
})
.on('swipedaction', function(e, direction) {
if (direction === 'left') {
// Swiped left, move right
} else {
// Swiped right, move left
}
});
评论
e.changedTouches[0]
e.originalEvent.changedTouches[0]
touchstartX
$(this).touchstartX = e.changedTouches[0].screenX;
let $self
.on('touchstart', function(e) {
$self = $(this); $self.touchStartX = e.originalEvent.changedTouches[0].screenX;
$(this)
$self
$('.slider')
e.changedTouches
e.originalEvent.changedTouches
class Carousel {
constructor(carouselWrapper, carouselItems, carouselPrev, carouselNext, dotContainer) {
this.carouselWrapper = document.querySelectorAll(carouselWrapper);
this.carouselItems = Array.from(document.querySelectorAll(carouselItems));
this.carouselPrev = document.querySelector(carouselPrev);
this.carouselNext = document.querySelector(carouselNext);
this.dotContainer = document.querySelector(dotContainer);
this.currentItem = 0;
this.maxItem = this.carouselItems.length;
this.isDragging = false;
this.startPos = 0;
this.currentTranslate = 0;
this.prevTranslate = 0;
this.#init();
}
#init() {
document.addEventListener('keydown', this.#keyBoardHandler.bind(this));
this.carouselPrev.addEventListener('click', this.#prevSlide.bind(this));
this.carouselNext.addEventListener('click', this.#nextSlide.bind(this));
this.dotContainer.addEventListener('click', this.#dotHandler.bind(this))
this.#createDots();
this.#gotoSlide(0)
this.#activeDots(0)
this.#touchHandler();
this.#disableoncontextmenu();
}
#touchHandler() {
this.carouselItems.forEach((slide, index) => {
const img = slide.querySelector('.carousel__bgimg');
if(!img) return;
img.addEventListener('dragstart', (e) => e.preventDefault());
slide.addEventListener('touchstart', this.#touchStart.bind(this));
slide.addEventListener('touchend', this.#touchEnd.bind(this));
slide.addEventListener('touchmove', this.#touchMove.bind(this));
});
}
#touchStart() {
this.isDragging = true;
this.startPos = this.#getpositionX(event);
}
#touchMove() {
if(this.isDragging) {
const currentPosition = this.#getpositionX(event);
this.currentTranslate = this.prevTranslate + currentPosition - this.startPos;
}
}
#touchEnd() {
this.isDragging = false;
const movedBy = this.currentTranslate - this.prevTranslate;
if(movedBy < -100) {
this.#nextSlide();
};
if(movedBy > 100) {
this.#prevSlide();
};
}
#getpositionX(event) {
return event.type.includes('mouse') ? event.pageX : event.touches[0].clientX;
}
#createDots() {
this.carouselItems.forEach((_, i) => {
this.dotContainer.insertAdjacentHTML('beforeend', `<div class="bullet" data-slide="${i}"></div>`)
});
}
#activeDots(slide) {
document.querySelectorAll('.bullet').forEach(function(dot) {
dot.classList.remove('active');
});
document.querySelector(`.bullet[data-slide="${slide}"]`)
.classList.add('active');
}
#gotoSlide(slide) {
this.carouselWrapper.forEach((s, i) => {
s.style.transform = `translate3d(${100 * (i - slide)}%, 0px, 0px)`;
});
}
#prevSlide() {
if(this.currentItem === 0) {
this.currentItem = this.maxItem - 1;
} else {
this.currentItem--;
};
this.#gotoSlide(this.currentItem);
this.#activeDots(this.currentItem);
}
#nextSlide() {
if(this.currentItem === this.maxItem -1) {
this.currentItem = 0;
} else {
this.currentItem++;
};
this.#gotoSlide(this.currentItem);
this.#activeDots(this.currentItem);
}
#dotHandler(e) {
if(e.target.classList.contains('bullet')) {
const { slide } = e.target.dataset;
this.#gotoSlide(slide);
this.#activeDots(slide);
}
}
#keyBoardHandler(e) {
if(e.keyCode === 39) this.#nextSlide();
e.keyCode === 37 && this.#prevSlide();
}
#disableoncontextmenu() {
this.carouselWrapper.forEach(function(item) {
item.oncontextmenu = function(event) {
event.preventDefault()
event.stopPropagation()
return false
}
});
}
}
document.addEventListener('DOMContentLoaded', function() {
const slider = new Carousel(
'.carousel__wrapper',
'.carousel__item',
'.prev-ctrl',
'.next-ctrl',
'.dots',
);
});
评论