在页面上监听所有滚动事件

34

背景:

我正在编写一个组件,该组件在点击时会打开一个子菜单。我无法知道此组件将放置在页面的哪个位置以及它将被嵌套在可能设置了overflow属性的区域中有多远。

考虑到溢出可能会裁剪子菜单,因此我将子菜单本身附加到body上,给它一个绝对位置,并通过代码将其链接到原始组件。这解决了溢出问题。

问题:

但是,如果用户滚动,子菜单将保持不变,而不是随其链接的组件一起移动,因此我需要能够监听页面上发生的任何和所有滚动事件,以便我可以适当地重新定位子菜单。

如果有一种简单的方法来监听所有滚动事件,或者如果有另一种更好的方法来执行此组件,我将感激任何建议。

我已经在JSFiddle上进行了测试,并设置了一个sandbox,但我没有成功,也没有在此网站或其他地方找到答案;尽管可能我使用了错误的搜索术语,但我想象不到我是第一个提出这个问题的人。

编辑

为了解决关闭投票,我不是在提供代码的情况下请求调试问题,也不是在问一个将来对任何人都没有帮助的问题。我正在询问如何监听发生在页面上的某种类型的所有事件,无论它们发生在哪里,我认为这是全球适用的,尽管可能是主观的。

编辑

$(window).on('scroll', function(){ /**/ }); 不是一个选项,因为它只监听窗口滚动,而不是任何嵌套滚动。

$('#ex1 #ex2').on('scroll', function(){ /**/ });不是一个选项,因为它要求实现代码的人意识到可能滚动的当前或可能的未来页面区域。


只需将滚动事件直接绑定到具有overflow: scroll样式的父元素即可。 - Joe
@Popnoodles,实际上没有“相关代码”,因为我不确定它是什么 - 这就是我的问题。 JSFiddle在那里提供一个视觉效果,以更好地理解页面可能具有的嵌套溢出。 - Hanna
1
@ Johannes ~ Joe所说的是监听特定滚动事件的最佳方法,而且不需要实现者知道任何东西。您只需要编写子菜单以重新绑定/删除其事件(如果它被放置/吸附到另一个元素上),就可以了。您的小部件只需要提供API调用,以允许将其移动到另一个元素上。 - Pebbl
@Johannes请考虑更新您的答案选择为我下面添加的答案(现在是最高评分)。这无疑是最好的答案,因为它依赖于简单、标准的capture参数,您可以在事件监听时传递,并且在所有主要浏览器中都得到支持。 - csuwldcat
@csuwldcat 感谢您提醒我,我很少回顾这些旧问题。我已经评估了您的答案,并认为它更加简洁明了,同时也能够满足需求,因此我已经接受了它。 - Hanna
显示剩余6条评论
4个回答

93

您应该能够使用第三个参数true,将文档级别的监听器附加到所有元素上以捕获滚动事件。这是它的示例:

document.addEventListener('scroll', function(e){ }, true);

结尾的true是重要的部分,它告诉浏览器在事件分发时捕获事件,即使该事件通常不会冒泡,例如change、focus和scroll。

下面是一个示例:http://jsbin.com/sayejefobe/1/edit?html,js,console,output


3
我希望我能够给这个答案点赞100次,谢谢! - Tim Santeford
我已更新示例以演示多个/嵌套区域,并简化了控制台日志记录,以便清楚地表明它按预期工作:http://jsbin.com/bexuhoyogu/1/edit?html,js,console,output - Hanna
1
真正的参数才是最重要的!谢谢! - Alexandre Magno Teles Zimerer
4
请注意,如果您想要移除事件监听器,需要在调用末尾再次添加 truedocument.removeEventListener('scroll', function(e){ }, true); 参考:https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/removeEventListener - Yulian

2

您需要确定滚动是发生在窗口级别还是元素级别。通常情况下,*应该足够。

$('*').scroll(function() {
    alert('scroll');
});

这是更新的链接:http://jsfiddle.net/wAadt/1


2
如何列出所有元素和窗口?
$('*').add(window).scroll(function() {
    console.log('scroll');
});

2
最好的方法是找出哪些元素可以滚动,然后将侦听器附加到它们上面。你可以在任何页面变化时运行这个函数,以确保你始终拥有所有可滚动的元素。
与在每个元素上使用侦听器相比(其他解决方案会这样做),这是一种性能上的优势:每次页面更新时,侦听器也会更新。如果有很多,这很快就会影响性能和内存使用。
更新后的演示:http://jsfiddle.net/ArtOfCode/wAadt/8/ 代码:
$("*").each(function() {
    if($(this).css("overflow") == "auto" || $(this).css("overflow") == "scroll") {
        $(this).scroll(function() {
            console.log("scroll");
        });
    }
});

(感谢@pebbl的帮助)

然后您可以将其包装在一个函数中,并在更改时运行该函数:

function addListeners() {
    $("*").each(function() {
        if($(this).css("overflow") == "auto" || $(this).css("overflow") == "scroll") {
            $(this).css('border', '1px solid red').scroll(function() {
                console.log("scroll");
            });
        }
    });
}

$("body").on("change",function()
    addListeners();
}

可以承认这有点复杂,但它尽可能少地使用事件监听器来解决问题。


你的方法并没有达到你的预期效果。实际上,它会将滚动监听器绑定到页面上的每个元素。 - Pebbl
我刚刚又看了一遍,并更新了fiddle以显示监听器的位置,请再次检查。只有可滚动的元素有监听器,例如并非所有的<p>都有。 - ArtOfCode
你的条件语句用于过滤是没问题的,但它并不是作为.scroll()上下文使用的。查看这个更新的fiddle,你会发现,基本上$(this).add()构造根本没有意义(没有接受无参数版本),而你所过滤的内容永远不会回到原始的$().scroll()。最好将其作为.filter()调用的一部分使用。 - Pebbl
请允许我参考我的第二个更新的fiddle作为证明。它应用了一个事件监听器和一个属性listened=true到每个与选择器匹配的元素;在检查这些元素时,可以明显看出只有可滚动的元素被选中。无论语法是否正确,它都能做到我所说的。 - ArtOfCode
抱歉,但它不起作用——请参见下面的fiddle版本,它可以正常工作(我添加了边框以可视化显示所选内容)。就像我说的,你的过滤器很好,是的,你在里面应用了你的属性(所以它们似乎起作用),但实际上你是将.scroll()应用于$($('*'))而不是过滤后的元素,这就是为什么我将.scroll()放在你过滤的部分内。http://jsfiddle.net/wAadt/7/。如果你检查我的之前的fiddle,你会发现将你的属性移到应用`.scroll()`的位置意味着每个元素都会应用该属性。 - Pebbl
啊,我明白了。抱歉——因为它在正确的位置应用了属性,所以对我来说似乎有效。谢谢,我会编辑原始内容。 - ArtOfCode

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接