【问题标题】:Detect new mouse wheel event检测新的鼠标滚轮事件
【发布时间】:2020-09-05 04:53:53
【问题描述】:

我正在使用以下事件侦听器来检测鼠标滚轮和滚动方向:

window.addEventListener('wheel', ({ deltaY }) => {
  console.log(deltaY);
  if (deltaY > 0) scrollDown();
  else if (deltaY < 0) scrollUp();
});

这里会发生以下情况:

  • Macbook 上的 2 指触摸板滚动触发事件处理程序
  • deltaY 由于滚动加速度计而不断记录
  • scrollDown()scrollUp() 继续发射直到加速度计停止

我只想在每次用户交互时触发一次 scrollUpscrollDown。因此,我需要检测 new 鼠标滚动事件,而不是 每个 鼠标滚动事件。这可能吗?

我确实尝试过超时来检测 deltaY 是否由于加速度计而仍在变化,但这还不够,因为如果它仍在变化,则第二次用户交互不会触发 scrollUpscrollDown

这是我想要实现的 CodePen:https://codepen.io/anon/pen/dQmPNN

它非常接近所需的功能,但是如果您在第一张幻灯片上用力敲击鼠标滚轮,然后尝试立即滚动到下一张,超时解决方案会将其锁定,因此您必须再等一秒钟左右,直到超时完成,您可以继续滚动。

【问题讨论】:

  • 向我们展示你在超时时尝试了什么。您将需要对事件处理进行限制或去抖动,请参阅此处了解两种方法之间的区别:stackoverflow.com/questions/25991367/…
  • @misorude 如果您认为它仍然相关,很高兴分享我的超时尝试,但我要说的是,简单的去抖动或限制在这里是不够的,因为它们实际上都没有检测到新的用户交互,他们只会等到加速度计完成,然后允许另一个交互。
  • 啊,所以您希望它们继续滚动,但在它们开始滚动时只触发一次向上/向下功能?然后你应该记住他们之前滚动的方向,并将其与当前处理程序执行中的方向进行比较。

标签: javascript mousewheel


【解决方案1】:

这是旧的,但我在寻找几乎相同问题的答案时发现了它。 我为自己的目的解决了这个问题,所以这是我的解决方案,以防它帮助其他人。

真正的问题在于定义什么是一个连续的动作。如果没有更具体的工作,这只是时间问题。关键是事件之间的时间 - 所以算法是不断累积事件,直到它们之间存在一定的差距。然后剩下的就是弄清楚允许的差距应该有多大,这是特定于解决方案的。这就是用户停止滚动直到他们得到反馈后的最大延迟。我的最佳值是四分之一秒,我在下面使用它作为默认值。

下面是我的 JavaScript,我正在使用 jQuery 将事件附加到 id 为“wheelTestDiv”的 div,但它与窗口对象的工作方式相同,如问题所示。

值得注意的是,下面会查找任何 onWheel 事件,但仅跟踪 Y 轴。如果您需要更多轴,或者只想在 deltaY 发生更改时将事件计数到计时器中,则需要适当地更改代码。

另外值得注意的是,如果您不需要针对不同 DOM 对象跟踪事件的灵活性,您可以将类重构为具有静态方法和属性,因此无需创建全局对象变量。如果您确实需要跟踪不同的 DOM 对象(我需要),那么您可能需要该类的多个实例。

"use strict";
class MouseWheelAggregater {
    // Pass in the callback function and optionally, the maximum allowed pause
    constructor(func, maxPause) {
        this.maxAllowedPause = (maxPause) ? maxPause : 250; // millis
        this.last = Date.now();
        this.cummulativeDeltaY = 0;
        this.timer;
        this.eventFunction = func;
    }
    
    set maxPause(pauseTime) {
        this.maxAllowedPause = pauseTime;
    }

    eventIn(e) {
        var elapsed = Date.now() - this.last;
        this.last = Date.now();
        if ((this.cummulativeDeltaY === 0) || (elapsed < this.maxAllowedPause)) {
            // Either a new action, or continuing a previous action with little
            // time since the last movement
            this.cummulativeDeltaY += e.originalEvent.deltaY;
            if (this.timer !== undefined) clearTimeout(this.timer);
            this.timer = setTimeout(this.fireAggregateEvent.bind(this), 
                this.maxAllowedPause);
        } else { 
            // just in case some long-running process makes things happen out of 
            // order
            this.fireAggregateEvent();
        }
    }

    fireAggregateEvent() {
        // Clean up and pass the delta to the callback
        if (this.timer !== undefined) clearTimeout(this.timer);
        var newDeltaY = this.cummulativeDeltaY;
        this.cummulativeDeltaY = 0;
        this.timer = undefined;
        // Use a local variable during the call, so that class properties can
        // be reset before the call.  In case there's an error.
        this.eventFunction(newDeltaY);
    }
}

// Create a new MouseWheelAggregater object and pass in the callback function,
// to call each time a continuous action is complete.
// In this case, just log the net movement to the console.
var mwa = new MouseWheelAggregater((deltaY) => {
    console.log(deltaY);
});

// Each time a mouse wheel event is fired, pass it into the class.
$(function () {
    $("#wheelTestDiv").on('wheel', (e) => mwa.eventIn(e));
});

网页 ...

<!DOCTYPE html>
<html>
  <head> 
    <title>Mouse over test</title>
    <script src="/mouseWheelEventManager.js"></script>
  </head> 
  <body>
    <div id="wheelTestDiv" style="margin: 50px;">Wheel over here</div>
  </body>
</html>

【讨论】:

    【解决方案2】:

    您是否尝试过将其分解为带有标志的函数以检查是否发生了交互?

    例如:

    // Create a global variable which will keep track of userInteraction
    let shouldScroll = true;
    
    // add the event listener, and call the function when triggered
    window.addEventListener('wheel', () => myFunction());
    
    //Create a trigger function, checking if shouldScroll is true or false.
    myFunction(){
        shouldScroll ? (
            if (deltaY > 0) scrollDown();
            else if (deltaY < 0) scrollUp();
            // Change back to false to prevent further scrolling. 
            shouldScroll = false;
        ) : return;
    }
    
    /* call this function when user interaction occurs
     and you want to allow scrolling function  again.. */
    userInteraction(){
        // set to true to allow scrolling
        shouldScroll = true;
    }
    

    【讨论】:

    • 这有点类似于我创建的超时解决方案。您如何检测下一个 deltaY 变化是来自上一个滚动的加速度计还是来自新的用户交互?这就是问题所在。
    • 查看我添加到原始问题中的 CodePen 以演示问题。
    【解决方案3】:

    我们可以通过延迟执行和删除延迟之间的事件来避免这种情况,参考下面的示例并添加了 1000ms 作为延迟,可以根据您的要求进行修改。

            let scrollPage = (deltaY)=>{
            console.log(deltaY);
            if (deltaY > 0) scrollDown();
            else if (deltaY < 0) scrollUp();
            };
    
            var delayReg;
            window.addEventListener('wheel', ({ deltaY }) => {
                clearTimeout(delayReg);
                delayReg = setTimeout(scrollPage.bind(deltaY),1000);
            });
    

    【讨论】:

    • 是的,这几乎就是我尝试过的超时解决方案。这里的问题是,任何新的用户滚动交互都应该触发预期的功能。在这种情况下,如果用户在加速度计完成后 1000 毫秒内再次滚动,则不会触发 scrollDownscrollUp 函数。
    • 即如果你有一个非常灵敏的鼠标滚轮并且第一次真的用力敲击它,加速度计可能会持续几秒钟,所以用户在这 2 秒加上 1000 毫秒超时期间被锁定,无法与另一个滚动交互。所以他们在 3s 内什么都做不了。
    • 我已将 CodePen 添加到我正在尝试做的事情的原始帖子中,以及超时实现,并进一步解释了问题。
    • @Coop 哦,明白了。 AFAIK 没有比使用超时更好的解决方案了。
    • 我认为可能是这种情况,但我想知道一些网站如何处理这样的布局,他们劫持滚动。
    猜你喜欢
    • 2021-03-07
    • 2014-12-30
    • 2022-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-28
    相关资源
    最近更新 更多