如何处理页面上任意位置的点击事件,即使某个元素停止了事件传播?

75
我们正在开发一个JavaScript工具,其中包含旧代码,因此我们无法重写整个工具。
现在,一个菜单被添加到底部并固定位置,客户非常希望它有一个切换按钮来打开和关闭菜单, 但是当用户在菜单外进行操作时需要自动关闭,例如当用户返回到页面,并选择某些内容或单击表单字段时。
这可以通过在body上使用click事件来实现,任何点击都会触发该事件, 但是在旧代码中有许多项目,其中在内部链接上处理了点击事件,并且在点击函数中添加了return false,以防止网站继续转到链接的href标记。
因此,显然,这样的通用函数确实有效,但是在内部链接上点击时不起作用,因为return false会停止传播。
$('body').click(function(){
  console.log('clicked');
});

有没有办法无论如何强制触发body的点击事件, 或者有其他方法让菜单消失,使用全局点击事件或类似的任何东西?

而不必重写应用程序中创建多年的所有其他点击操作。 那将是一个巨大的任务,特别是因为我不知道我该如何重写它们,没有返回false,但仍然不让它们进入它们的 href

10个回答

102

现代DOM实现中的事件有两个阶段,捕获冒泡。 捕获阶段是第一个阶段,从文档的defaultView流向事件目标,然后是冒泡阶段,从事件目标返回到defaultView。 要了解更多信息,请参见http://www.w3.org/TR/DOM-Level-3-Events/#event-flow

要处理事件的捕获阶段,您需要将addEventListener的第三个参数设置为true

document.body.addEventListener('click', fn, true); 

很遗憾,正如Wesley所提到的,事件捕获阶段在旧版浏览器中无法可靠地或根本不能处理。

一种可能的解决方案是处理mouseup事件,因为点击的事件顺序为:

  1. mousedown
  2. mouseup
  3. click

如果你确定没有取消mouseup事件的处理程序,那么这是一种方法(也可以说是更好的方法)。另一个需要注意的事情是许多(如果不是大多数)UI菜单会在鼠标按下时消失。


3
对于任何想知道是否可以使用此功能的人,这里是 caniuse 页面:http://caniuse.com/#feat=addeventlistener 简短的回答是可以,只要你不关心 IE8。 - Jason Axelson

8

this是关键点(相对于evt.target)。请参阅示例。

document.body.addEventListener("click", function (evt) {
    console.dir(this);
    //note evt.target can be a nested element, not the body element, resulting in misfires
    console.log(evt.target);
    alert("body clicked");
});
<h4>This is a heading.</h4>
<p>this is a paragraph.</p>


7
与 Andy E 合作,这是力量的黑暗面:
var _old = jQuery.Event.prototype.stopPropagation;

jQuery.Event.prototype.stopPropagation = function() {
    this.target.nodeName !== 'SPAN' && _old.apply( this, arguments );
};     
示例http://jsfiddle.net/M4teA/2/ 请记住,如果所有事件都是通过jQuery绑定的,您可以在此处处理这些情况。在此示例中,如果我们没有处理,我们只需调用原始的.stopPropagation()
您不能阻止防止,不行。
您可以手动在代码中重新编写这些事件处理程序。这是棘手的事情,但如果您知道如何访问存储的处理程序方法,则可以解决它。我稍微尝试了一下,这是我的结果:
$( document.body ).click(function() {
    alert('Hi I am bound to the body!');
});

$( '#bar' ).click(function(e) {
    alert('I am the span and I do prevent propagation');
    e.stopPropagation();
});

$( '#yay' ).click(function() {
    $('span').each(function(i, elem) {
        var events        = jQuery._data(elem).events,
            oldHandler    = [ ],
            $elem         = $( elem );

        if( 'click' in events ) {                        
            [].forEach.call( events.click, function( click ) {
                oldHandler.push( click.handler );
            });

            $elem.off( 'click' );
        }

        if( oldHandler.length ) {
            oldHandler.forEach(function( handler ) {
                $elem.bind( 'click', (function( h ) {
                    return function() {
                        h.apply( this, [{stopPropagation: $.noop}] );
                    };
                }( handler )));
            });
        }
    });

    this.disabled = 1;
    return false;
});

示例: http://jsfiddle.net/M4teA/

请注意,上面的代码仅适用于jQuery 1.7。如果这些点击事件是使用早期版本的jQuery或"内联"绑定的,则仍然可以使用该代码,但您需要以不同的方式访问"旧处理程序"。

我知道我在这里假设了很多"完美世界"的情况,例如,那些句柄明确调用.stopPropagation()而不是返回false。因此,它可能仍然是一个无用的学术示例,但我感觉要提出它 :-)

编辑:嘿,return false;将正常工作,事件对象以相同的方式被访问。


3

如果您确保这是第一个事件处理程序的工作,类似以下的代码可能会有所帮助:

$('*').click(function(event) {
    if (this === event.target) { // only fire this handler on the original element
        alert('clicked');
    }
});

请注意,如果页面中有大量元素,这将非常缓慢,并且对于任何动态添加的内容都不起作用。

动态的东西可以通过 $("*").on("click", function() {}); 很容易地解决,但无论如何这都不是一个好主意。 - Bbit

2

我知道这是一个老问题,但是为了补充@lonesomeday的答案,你可以使用普通JavaScript来完成相同的操作:

document.querySelectorAll('*')
  .forEach(element => element.addEventListener('click', e => {
    console.log('clicked: ', e.target)
  }))

这将会把监听器添加到每一个元素上,而不是整个页面,根据经验,这样做可以让你即使在页面正在导航或者已经有一个包含 stopPropagation 的 onclick 时,仍能执行 click 事件。


2

1
你可以使用jQuery在文档DOM上添加事件监听器。

    $(document).on("click", function () {
        console.log('clicked');
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


1

我认为这就是你需要的:

$("body").trigger("click");

这将允许您从任何地方触发 body 点击事件。

如果DOM中更深层次的内容阻止了事件传播,那么这个事件会触发吗? - Matt
我认为问题在于某些链接的事件处理程序通过返回false来吞噬了点击事件,而不是无法生成点击事件。 - Wesley Petrowski

0

0

我的修复在2023年2月:

在页面/文档的任何位置触发函数的方法:

JS代码:

document.onmouseup = closeMenus

“closeMenus” 是一个函数,它将每个菜单的显示值设置为“无”。
在文档上任何地方发生“mouseup”事件时,都会调用该函数。

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