如何检测用户用JavaScript在网页上向某个方向滑动手指?
我想知道是否有一种解决方案可以同时适用于iPhone和Android手机上的网站。
如何检测用户用JavaScript在网页上向某个方向滑动手指?
我想知道是否有一种解决方案可以同时适用于iPhone和Android手机上的网站。
当前回答
一个如何使用偏移量的例子。
// 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] }
其他回答
根据@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();
如果你只需要滑动,你最好只使用你需要的部分。 这应该适用于任何触摸设备。
这是经过gzip压缩,缩小,babel等大约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;
我重做了@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);
};
我重做了@ruben-martinez的答案,使用来自@givanse的惊人的解决方案,使用自定义反应钩子处理滑动事件。
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);
}
我在实现他的答案时遇到的主要挑战是不知道如何将swipe元素的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;
};
}