如何在不将 onscroll 处理程序附加到每个容器的情况下捕获页面上的所有滚动事件

Posted

技术标签:

【中文标题】如何在不将 onscroll 处理程序附加到每个容器的情况下捕获页面上的所有滚动事件【英文标题】:How to capture all scrolling events on a page without attaching an onscroll handler to every single container 【发布时间】:2012-10-22 11:53:01 【问题描述】:

考虑以下网页:

 <html>
   <body onscroll="alert('body scroll event')">
     <div style='width:200px;height:200px;overflow:auto' onscroll="alert('div scroll event')">
       <div style='height:400px'>
       </div>
     </div>
   </body>
 </html>

这个 html 创建一个带有滚动条的 div。如果移动滚动条,则会触发 div 元素上的onscroll 事件。但是,不会触发主体上的 onscroll 事件。这是意料之中的,因为 W3C 声明元素 onscroll 事件不会“冒泡”。

但是,我正在开发一个客户端 Web 框架,该框架需要知道任何时候滚动页面的任何元素上的滚动条。如果onscroll 冒泡,这很容易做到,但不幸的是它没有。有没有其他方法可以检测整个页面中的onscroll 事件? (现在我主要关注 Webkit,所以一个特定于 Webkit 的解决方案会很好......)

以下是我尝试过的一些事情:

    正在捕获 DOMAttrModified(似乎不会因为移动滚动条而触发。) 使用 DOM 观察者(似乎也不会触发滚动条) 将onscroll 事件类型更改为冒泡(似乎不可能)

似乎在全局范围内捕获onscroll 事件的唯一方法是将onscroll 事件附加到每个可能滚动的元素,这非常难看并且会损害我的框架的性能。

有人知道更好的方法吗?

【问题讨论】:

window.onscroll 不起作用? 【参考方案1】:

*...蟋蟀鸣叫... *

好的,我想这个问题不会得到任何 *** 的喜爱,所以我不妨就我目前找到的最佳解决方案回答我自己的问题,以防其他用户偶然发现这个问题:

我想出的最佳解决方案是为 BODY 元素捕获“onmousedown”和“onkeydown”:这些事件冒泡,因此如果用户尝试在页面上移动滚动条,这些全局函数将触发为一种副产品。然后,在这些函数中,只需查找 event.target 并将临时“onscroll”事件附加到这些对象,直到鼠标/键再次“向上”。使用该方法,您可以避免“处理程序膨胀”并仍然全局捕获所有“onscroll”事件。 (我认为这也适用于“鼠标滚轮”滚动,但我对最终皱纹的研究仍在进行中。)

【讨论】:

你还应该在文档中添加一个鼠标滚轮事件。 很好 - 我没有进入整个答案,但只要你提到“onmousedown”,我就已经指出了正确的方向 - 谢谢【参考方案2】:

我也有同样的问题。

最简单的方法当然是使用 jQuery。 请注意,此方法可能会显着降低您的页面速度。此外,它不会考虑绑定事件后添加的任何新元素。

$("*").scroll(function(e) 
    // Handle scroll event
);

在原生 javascript 中,您可以在 addEventListener 调用中将 useCapture 布尔值设置为 true,它将触发所有元素,包括动态添加的元素。

document.addEventListener('scroll', function(e) 
    // Handle scroll event
, true);

请注意,这将在滚动事件实际发生之前触发。据我了解,事件有两个阶段。捕获阶段首先发生,从页面根(ownerDocument?)开始,向下遍历到事件发生的元素。在此之后是冒泡阶段,它从元素返回到根。

一些快速测试也表明这可能是 jQuery 处理它的方式(至少用于跟踪所有页面元素上的滚动),但我不是 100% 确定。

这是一个展示原生 JavaScript 方法 http://jsfiddle.net/0qpq8pcf/ 的 JSFiddle

【讨论】:

小心第一种方法,因为将事件侦听器附加到每个单独的 DOM 元素会使事情变得非常缓慢(更不用说这不考虑在创建事件处理程序后添加的元素)。第二个可能更可取,但是如果可能的话,您也应该在不需要时将其删除(例如,如果您只想知道何时关闭弹出窗口或其他内容,则仅在弹出窗口打开时附加它,并在弹出窗口时将其删除关闭)。 我试图找出哪个元素在进行滚动(因此在它因 CSS 更改而中断后附加一个滚动处理程序),第一种方法没有给我任何东西。原来,它是document 本身,所以之前处理程序附加了“主体”并且停止工作。第二种方法帮助我发现确实是文档/窗口本身在滚动。【参考方案3】:

在现代浏览器中检测所有滚动事件的最简单方法是在附加事件时使用“捕获”而不是“冒泡”:

window.addEventListener('scroll', function() code goes here , true)

不幸的是,据我所知,在 等旧版浏览器中没有等效功能

【讨论】:

非常感谢这个【参考方案4】:

当您想要在后台滚动任何内容后关闭对话框时,以下方法可以正常工作:

var scrollListener = function(e) 
    // TODO: hide dialog
    document.removeEventListener('scroll', scrollListener, true);
;

document.addEventListener('scroll', scrollListener, true);

【讨论】:

【参考方案5】:

我需要处理在 shadowRoots 中具有滚动事件的复杂自定义元素中的任何上下文中的滚动。这至少涵盖了我的场景中的部分问题,并且通常在这些较新的 Web 组件 API 的上下文中借鉴这里的答案和 cmets。到目前为止,在适当的生命周期回调中附加和分离侦听器效果很好(曾经可能不适合您的用例)。

      self.addEventListener('mousewheel', handler, capture: true, once: true);
      self.addEventListener('keydown', handler, capture: true, once: true);

还要注意event.composedPath() 提供了通过所有 shadowRoots 和所有节点的整个事件路径,如果需要有关滚动上下文的细节。如果是这种情况,使用这种方法并将针对特定场景的新处理程序附加到感兴趣的节点可能是有意义的。

【讨论】:

以上是关于如何在不将 onscroll 处理程序附加到每个容器的情况下捕获页面上的所有滚动事件的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在不将 dtype 更改为对象的情况下将 NaT 附加到带有时区的 pandas 日期时间?

如何在不将应用程序放在前面的情况下从 BLE 设备控制 iOS 应用程序进行后台处理

如何在不将批处理文件复制到 c 中的 7zip 文件夹的情况下运行 7zip 批处理

如何在不附加到 DOM 的情况下正确删除 html5 音频?

谷歌分析跨域跟踪而不将 Cookie 附加到 URL?

在不将它们置于前台的情况下进行应用间通信的最佳方法?