阻止鼠标滚轮事件在OSX中发生两次

stopping mousewheel event from happening twice in OSX

本文关键字:两次 OSX 鼠标 事件      更新时间:2023-09-26

我注意到鼠标滚轮事件在mac-osx中发生了多次。可以归结为惯性特性。

有办法纠正这种行为吗?

(自签名ssl,请放心!)https://sandbox.idev.ge/roomshotel/html5_v2/

我使用的是scrollSections.jshttps://github.com/guins/jQuery.scrollSections

它使用了mousewheel jquery插件:https://github.com/brandonaaron/jquery-mousewheel

我看到很多人都有同样的问题:https://github.com/brandonaaron/jquery-mousewheel/issues/36

有一些解决方案,但没有一个适用于scrollSections插件。

有什么想法可以从JS中禁用这个惯性功能吗?

我尝试修复:

// Fix for OSX inertia problem, jumping sections issue.
if (isMac) {
  var fireEvent;
  var newDelta = deltaY;
  if (oldDelta != null) {
    //check to see if they differ directions
    if (oldDelta < 0 && newDelta > 0) {
      fireEvent = true;
    }
    //check to see if they differ directions
    if (oldDelta > 0 && newDelta < 0) {
      fireEvent = true;
    }
    //check to see if they are the same direction
    if (oldDelta > 0 && newDelta > 0) {
      //check to see if the new is higher
      if (oldDelta < newDelta) {
        fireEvent = true;
      } else {
        fireEvent = false;
      }
    }
    //check to see if they are the same direction
    if (oldDelta < 0 && newDelta < 0) {
      //check to see if the new is lower
      if (oldDelta > newDelta) {
        fireEvent = true;
      } else {
        fireEvent = false;
      }
    }
  } else {
    fireEvent = true;
  }
  oldDelta = newDelta;
} else {
  fireEvent = true;
}

您可以在此处看到已实现的修复:https://sandbox.idev.ge/roomshotel/html5_v2/但这是一次偶然的机会。

最新的超时解决方案有一个主要缺点:动态滚动效果可能会持续很长时间(甚至1秒左右)。。。禁用滚动1-2秒并不是最好的决定。

Sooo,正如承诺的那样,这是另一种方法。

我们的目标是为one用户操作提供一个响应,在本例中为滚动。

什么是"一次滚动"为了解决这个问题,我们假设"一次滚动"是一个从页面开始移动到移动结束的事件。

动态滚动效果是通过在小距离内多次移动页面(例如,每20ms)来实现的。这意味着我们的动态滚动由许多小的线性"滚动"组成。

经验测试表明,这种小的"滚动"在动态滚动的中间每17-18ms发生一次,在开始和结束时约80-90ms。下面是一个简单的测试,我们可以设置它来查看:

var oldD;
var f = function(){
    var d = new Date().getTime();
    if(typeof oldD !== 'undefined')
        console.log(d-oldD);
    oldD = d;
}
window.onscroll=f;

重要!每次发生这种小滚动时,都会触发滚动事件所以:

 window.onscroll = function(){console.log("i'm scrolling!")};

将在一个动态涡旋期间发射15到20+次。顺便说一句,onscroll有非常好的浏览器支持(见兼容性表),所以我们可以依赖它(除了触摸设备,我稍后会讨论这个问题);

有些人可能会说,重新定义window.onscroll并不是设置事件侦听器的最佳方式。是的,我们鼓励您使用

 $(window).on('scroll',function(){...});

或者不管你喜欢什么,这都不是问题的关键(我个人使用我自己写的图书馆)。

因此,在onscroll事件的帮助下,我们可以可靠地判断页面的这种特定的迷你移动是属于一个持久的动态滚动,还是一个新的滚动:

    var prevTime = new Date().getTime();
    var f = function(){
        var curTime = new Date().getTime();
        if(typeof prevTime !== 'undefined'){
            var timeDiff = curTime-prevTime;
            if(timeDiff>200)
                console.log('New kinetic scroll has started!');
        }
        prevTime = curTime;
    }
    window.onscroll=f;

您可以调用所需的回调函数(或事件处理程序),而不是"console.log",就完成了!该功能将只在每个动态或简单滚动上启动一次,这是我们的目标。

你可能已经注意到,我用200毫秒作为判断它是新卷轴还是前一卷轴的一部分的标准。这取决于您将其设置为更大的值,即999%,以确保防止任何额外的呼叫。但是,请记住,这不是我们在之前的回答中使用的。这只是任何两页移动之间的一段时间(无论是新滚动还是动态滚动的一小部分)。在我看来,在动态滚动中,两步之间的滞后时间超过200ms的可能性很小(否则将根本不平滑)。

正如我上面提到的,onscroll事件在触摸设备上的工作方式不同。它不会在动力滚动的每一小步中都开火。但当页面的移动最终结束时,它就会启动。此外,还有ontouchmove事件。。。所以,这没什么大不了的。如果需要,我也可以为触摸设备提供解决方案。

附言:我知道我写得太多了,所以我很乐意回答您的所有问题,如果您需要,我会提供更多的代码

所提供的解决方案在所有浏览器中都受支持,非常轻量级,更重要的是,它不仅适用于Mac,而且适用于所有可能实现动态滚动的设备,所以我认为这真的是一条路要走。

你知道,我认为在这种情况下使用超时是一个更好的主意。为什么不写这样的东西:

// Let's say it's a global context or whatever...:
var fireEvent = true;
var newDelta, oldDelta, eventTimeout;
newDelta = oldDelta = eventTimeout = null;
// ... and the function below fires onmousewheel or anything similar:
function someFunc(){
    if(!fireEvent) return; // if fireEvent is not allowed => stop execution here ('return' keyword stops execution of the function), else, execute code below:
    newDelta = deltaY;
    if(oldDelta!=null&&oldDelta*newDelta>0){ // (1.1) if it's not the first event and directions are the same => prevent possible dublicates for further 50ms:
        fireEvent = false;
        clearTimeout(eventTimeout); // clear previous timeouts. Important!
        eventTimeout = setTimeout(function(){fireEvent = true},500);
    }
    oldDelta = newDelta;
    someEventCallback(); // (1.2) fire further functions...
    }

因此,如果在与上一次相同的方向上调用鼠标滚轮事件(请参见1.1中的条件),则在上一次鼠标滚轮事件调用后半秒内触发的任何鼠标滚轮事件都将被忽略。这将解决问题,用户不可能发现这一点。延迟金额可能会更改,以更好地满足您的需求。

该解决方案是在纯JS上制作的。欢迎您就将其集成到您的环境中提出任何问题,但我需要您提供页面的进一步代码。

附言:我在您的代码中没有看到任何类似于eventCallback()调用的内容(请参阅我的解决方案的1.2)。只有fireEvent旗。你是在做这样的事情吗:

if(fireEvent)
    someEventCallback();

晚些时候还是什么?

p.p.S.注意,fireEvent应该在全局范围内,以便在这里使用setTimeout。如果不是这样,也很容易使其正常工作,但代码需要修改一点。如果是你的情况,告诉我,我会帮你解决的。

更新

经过简短的搜索,我发现Underscore的_debound()函数中也使用了类似的机制。请参阅此处的Undercore文档

您是否想过使用fullpage.js?它在到达一个部分和滚动到下一个部分之间有一段延迟,这解决了Mac用户使用触控板或苹果魔法鼠标时遇到的部分问题。

它还将为您提供一些其他好处,例如更多的选项、方法以及与触摸设备和不支持CSS3的旧浏览器的兼容性。

要开始,让我们缩短您的解决方案(因此更容易理解和调试):

var fireEvent;
var newDelta = deltaY;
var oldDelta = null;
fireEvent = EventCheck();
oldDelta = newDelta;
function EventCheck(){
    if(oldDelta==null) return true; //(1.1)
    if(oldDelta*newDelta < 0) return true; // (1.2) if directions differ => fire event
    if(Math.abs(newDelta)<Math.abs(oldDelta)) return true; // (1.3) if oldDelta exceeds newDelta in absolute values => fire event
    return false; // (1.4) else => don't fire;
}

正如您所看到的,它的作用与您的代码完全相同。然而,我无法理解您代码的这一部分(对应于我的代码片段中的(1.3)):

 //check to see if the new is lower
 if (oldDelta > newDelta) {
    fireEvent = true;
 } else {
    fireEvent = false;
 }

从提供的代码来看,deltaY是如何计算的还不清楚。可以假设,delta等于endPosition-initialPosition。因此,oldDelta>newDelta并不意味着新的位置更低,而是这两个值之间的新差距更大。如果这就是它的意思,并且你仍然使用它,我想你可以试着用它来追踪惯性。然后您应该更改比较运算符(使用小于,而不是大于,反之亦然)。换句话说,我会写:

if(Math.abs(newDelta)>Math.abs(oldDelta)) return true; // (1.3) 

你看,现在我使用了"大于"运算符,这意味着:newDelta在绝对值上超过oldDelta=>这不是惯性,你仍然可以触发事件。

这是你想要实现的,还是我误解了你的代码?如果是,请解释deltaY是如何计算的,以及通过比较旧的&新Deltas。附言:我建议在这一步中不要使用if(isMac),而问题也可能隐藏在那里。