如何检测元素外部的点击?

How do I detect a click outside an element?

提问人: 提问时间:9/30/2008 最后编辑:6 revs, 3 users 50%Sergio del Amo 更新时间:3/28/2023 访问量:1720603

问:

我有一些 HTML 菜单,当用户单击这些菜单的头部时,我会完全显示这些菜单。我想在用户单击菜单区域之外时隐藏这些元素。

jQuery可以做这样的事情吗?

$("#menuscontainer").clickOutsideThisElement(function() {
    // Hide the menus
});
javascript jquery 单击

评论

51赞 Ted 11/10/2011
下面是此策略的示例: jsfiddle.net/tedp/aL7Xe/1
22赞 Jon Coombs 1/3/2015
正如 Tom 所提到的,在使用此方法之前,您需要阅读 css-tricks.com/dangers-stopping-event-propagation。不过,这个 jsfiddle 工具很酷。
3赞 Rohit Kumar 7/8/2015
获取对元素的引用,然后是 event.target,最后是 != 或 ==,然后它们都相应地执行代码。
0赞 Dan Philip Bejoy 4/14/2017
尝试使用 .http://stackoverflow.com/questions/152975/how-do-i-detect-a-click-outside-an-element/43405204#43405204event.path
9赞 lowtechsun 4/23/2017
不带 .event.targetevent.stopPropagation

答:

24赞 Chris MacDonald #1

检查窗口单击事件目标(只要它未在其他任何地方捕获,它就应传播到窗口),并确保它不是任何菜单元素。如果不是,那么你就不在菜单范围内。

或者检查单击的位置,并查看它是否包含在菜单区域中。

2011赞 11 revs, 9 users 43%Eran Galperin #2

注意:使用是应该避免的,因为它会破坏 DOM 中的正常事件流。有关详细信息,请参阅此 CSS 技巧文章。请考虑改用此方法stopPropagation

将单击事件附加到文档正文,以关闭窗口。将单独的单击事件附加到容器,以停止传播到文档正文。

$(window).click(function() {
  //Hide the menus if visible
});

$('#menucontainer').click(function(event){
  event.stopPropagation();
});

评论

811赞 Art 6/12/2010
这打破了 #menucontainer 中包含的许多事物(包括按钮和链接)的标准行为。我很惊讶这个答案如此受欢迎。
83赞 Eran Galperin 6/17/2010
这不会破坏 #menucontainer 内部任何内容的行为,因为它位于其中任何内容的传播链的底部。
102赞 meo 2/25/2011
它非常美丽,但你不应该使用身体。身体总是有其内容的高度。它的内容不多或屏幕很高,它只适用于身体填充的部分。$('html').click()
114赞 Andre 5/8/2012
我也很惊讶这个解决方案得到了这么多的选票。对于外部具有 stopPropagation 的任何元素,此操作都将失败 jsfiddle.net/Flandre/vaNFw/3
145赞 Tom 5/20/2014
菲利普·沃尔顿(Philip Walton)很好地解释了为什么这个答案不是最佳解决方案:css-tricks.com/dangers-stopping-event-propagation
135赞 4 revs, 4 users 93%Joe Lencioni #3

我有一个应用程序,其工作方式与 Eran 的示例类似,只是在打开菜单时将单击事件附加到正文......有点像这样:

$('#menucontainer').click(function(event) {
  $('html').one('click',function() {
    // Hide the menus
  });

  event.stopPropagation();
});

有关 jQuery 的 one() 函数的更多信息

评论

10赞 vsync 8/18/2009
但是,如果您单击菜单本身,那么在外面,它将无法:)
171赞 2 revs, 2 users 77%ivan quintero #4

这里的其他解决方案对我不起作用,所以我不得不使用:

if(!$(event.target).is('#foo'))
{
    // hide menu
}

编辑:纯Javascript变体(2021-03-31)

我使用这种方法来处理在单击下拉菜单时关闭下拉菜单。

首先,我为组件的所有元素创建了一个自定义类名。此类名称将添加到构成菜单小部件的所有元素中。

const className = `dropdown-${Date.now()}-${Math.random() * 100}`;

我创建了一个函数来检查点击和点击元素的类名。如果单击的元素不包含我在上面生成的自定义类名,则应将标志设置为,菜单将关闭。showfalse

const onClickOutside = (e) => {
  if (!e.target.className.includes(className)) {
    show = false;
  }
};

然后,我将单击处理程序附加到 window 对象。

// add when widget loads
window.addEventListener("click", onClickOutside);

...最后是一些家政服务

// remove listener when destroying the widget
window.removeEventListener("click", onClickOutside);

评论

47赞 honyovk 9/27/2012
这对我有用,除了我在语句中添加了内容,以便任何子元素在单击时都不会关闭菜单。&& !$(event.target).parents("#foo").is("#foo")IF
8赞 2 revs, 2 users 68%Erik #5

如果您正在为 IE 和 FF 3.* 编写脚本,并且您只想知道点击是否发生在某个框区域内,您还可以使用如下方法:

this.outsideElementClick = function(objEvent, objElement) {
  var objCurrentElement = objEvent.target || objEvent.srcElement;
  var blnInsideX = false;
  var blnInsideY = false;

  if (objCurrentElement.getBoundingClientRect().left >= objElement.getBoundingClientRect().left && objCurrentElement.getBoundingClientRect().right <= objElement.getBoundingClientRect().right)
    blnInsideX = true;

  if (objCurrentElement.getBoundingClientRect().top >= objElement.getBoundingClientRect().top && objCurrentElement.getBoundingClientRect().bottom <= objElement.getBoundingClientRect().bottom)
    blnInsideY = true;

  if (blnInsideX && blnInsideY)
    return false;
  else
    return true;
}

43赞 user212621 #6
$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

对我很好。

评论

1赞 OzzyTheGiant 11/16/2021
如果尝试将其与自定义构建的选择和选项菜单一起使用,则会在单击之前触发模糊,因此不会选择任何内容
39赞 2 revsWolfram #7

现在有一个插件:外部事件博客文章)

clickoutside 处理程序 (WLOG) 绑定到元素时,会发生以下情况:

  • 该元素被添加到一个数组中,该数组使用 clickoutside 处理程序保存所有元素
  • 命名空间单击处理程序绑定到文档(如果尚不存在)
  • 在文档中单击时,将为该数组中不等于 Click-Events 目标或不属于 Click-Events 目标的元素触发 ClickOutside 事件
  • 此外,clickoutside 事件的 event.target 设置为用户单击的元素(因此您甚至可以知道用户单击了什么,而不仅仅是他在外部单击)

因此,不会阻止任何事件的传播,并且可以在元素的“上方”使用额外的单击处理程序和外部处理程序。

2赞 3 revs, 3 users 64%govind #8

这对我来说效果很好:

$('body').click(function() {
    // Hide the menus if visible.
});

评论

2赞 ncubica 11/21/2014
问题在于,无论你点击哪里,你总是会让事件冒泡到身体上。所以你应该使用 event.stopPropagation,但这会破坏 DOM 行为的自然方式,我认为这不是一个好的做法
1616赞 25 revs, 21 users 25%Art #9

您可以侦听 click 事件,然后使用 .closest() 确保不是 click 元素的祖先或目标。document#menucontainer

如果不是,则单击的元素位于 之外,您可以安全地隐藏它。#menucontainer

$(document).click(function(event) { 
  var $target = $(event.target);
  if(!$target.closest('#menucontainer').length && 
  $('#menucontainer').is(":visible")) {
    $('#menucontainer').hide();
  }        
});

编辑 – 2017-06-23

如果您计划关闭菜单并希望停止侦听事件,也可以在事件侦听器之后进行清理。此函数将仅清理新创建的侦听器,保留 上的任何其他单击侦听器。使用 ES2015 语法:document

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    const $target = $(event.target);
    if (!$target.closest(selector).length && $(selector).is(':visible')) {
        $(selector).hide();
        removeClickListener();
    }
  }

  const removeClickListener = () => {
    document.removeEventListener('click', outsideClickListener);
  }

  document.addEventListener('click', outsideClickListener);
}

编辑 - 2018-03-11

对于那些不想使用jQuery的人。这是上面的普通 vanillaJS (ECMAScript6) 代码。

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
          element.style.display = 'none';
          removeClickListener();
        }
    }

    const removeClickListener = () => {
        document.removeEventListener('click', outsideClickListener);
    }

    document.addEventListener('click', outsideClickListener);
}

const isVisible = elem => !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js 

注意:这是基于 Alex 注释,仅使用而不是 jQuery 部分。!element.contains(event.target)

但现在也可以在所有主流浏览器中使用(W3C 版本与 jQuery 版本略有不同)。 Polyfills 可以在这里找到:Element.closest()element.closest()

编辑 - 2020-05-21

如果您希望用户能够在元素内部单击并拖动,然后在元素外部释放鼠标,而不关闭元素:

      ...
      let lastMouseDownX = 0;
      let lastMouseDownY = 0;
      let lastMouseDownWasOutside = false;

      const mouseDownListener = (event: MouseEvent) => {
        lastMouseDownX = event.offsetX;
        lastMouseDownY = event.offsetY;
        lastMouseDownWasOutside = !$(event.target).closest(element).length;
      }
      document.addEventListener('mousedown', mouseDownListener);

并在:outsideClickListener

const outsideClickListener = event => {
        const deltaX = event.offsetX - lastMouseDownX;
        const deltaY = event.offsetY - lastMouseDownY;
        const distSq = (deltaX * deltaX) + (deltaY * deltaY);
        const isDrag = distSq > 3;
        const isDragException = isDrag && !lastMouseDownWasOutside;

        if (!element.contains(event.target) && isVisible(element) && !isDragException) { // or use: event.target.closest(selector) === null
          element.style.display = 'none';
          removeClickListener();
          document.removeEventListener('mousedown', mouseDownListener); // Or add this line to removeClickListener()
        }
    }

评论

34赞 Pistos 4/6/2012
我尝试了许多其他答案,但只有这个有效。谢谢。我最终使用的代码是这样的: $(document).click( function(event) { if( $(event.target).closest('.window').length == 0 ) { $('.window').fadeOut('fast');
42赞 umassthrower 4/8/2012
实际上,我最终选择了这个解决方案,因为它更好地支持同一页面上的多个菜单,在打开第一个菜单时单击第二个菜单将使第一个菜单在 stopPropagation 解决方案中保持打开状态。
15赞 John 5/8/2013
很好的答案。当您有多个项目要关闭时,这是要走的路。
32赞 Alex Ross 7/31/2015
不使用 jQuery - 使用 Node.contains()!element.contains(event.target)
5赞 maxshuty 4/24/2022
如果你正在阅读这篇文章,那么你可能应该看看一些更现代的答案来解决这个问题,这些答案比这个答案更具可读性。
4赞 3 revs, 3 users 87%Neco #10

功能:

$(function() {
    $.fn.click_inout = function(clickin_handler, clickout_handler) {
        var item = this;
        var is_me = false;
        item.click(function(event) {
            clickin_handler(event);
            is_me = true;
        });
        $(document).click(function(event) {
            if (is_me) {
                is_me = false;
            } else {
                clickout_handler(event);
            }
        });
        return this;
    }
});

用法:

this.input = $('<input>')
    .click_inout(
        function(event) { me.ShowTree(event); },
        function() { me.Hide(); }
    )
    .appendTo(this.node);

而且功能非常简单:

ShowTree: function(event) {
    this.data_span.show();
}
Hide: function() {
    this.data_span.hide();
}

评论

0赞 Jānis Elmeris 8/20/2012
在单击容器的子元素时,这不会触发 clickout 事件吗?
19赞 2 revs, 2 users 97%Chu Yeow #11

我在这样的事情上取得了成功:

var $menuscontainer = ...;

$('#trigger').click(function() {
  $menuscontainer.show();

  $('body').click(function(event) {
    var $target = $(event.target);

    if ($target.parents('#menuscontainer').length == 0) {
      $menuscontainer.hide();
    }
  });
});

逻辑是:显示时,将单击处理程序绑定到正文,仅当(单击的)目标不是它的子项时才隐藏。#menuscontainer#menuscontainer

7赞 4 revs, 2 users 80%webenformasyon #12

用:

var go = false;
$(document).click(function(){
    if(go){
        $('#divID').hide();
        go = false;
    }
})

$("#divID").mouseover(function(){
    go = false;
});

$("#divID").mouseout(function (){
    go = true;
});

$("btnID").click( function(){
    if($("#divID:visible").length==1)
        $("#divID").hide(); // Toggle
    $("#divID").show();
});
28赞 4 revs34m0 #13

我不认为您真正需要的是当用户单击外部时关闭菜单;您需要的是当用户单击页面上的任意位置时关闭菜单。如果你点击菜单,或者离开菜单,它应该关闭,对吧?

上面没有找到令人满意的答案,这促使我前几天写了这篇博文。对于更迂腐的人来说,有许多陷阱需要注意:

  1. 如果在单击时将单击事件处理程序附加到 body 元素,请确保在关闭菜单并取消绑定事件之前等待第二次单击。否则,打开菜单的单击事件将冒泡到必须关闭菜单的侦听器。
  2. 如果对点击事件使用 event.stopPropogation(),则页面中的其他元素都不能具有“单击任意位置关闭”功能。
  3. 将单击事件处理程序无限期地附加到 body 元素不是高性能的解决方案
  4. 将事件的目标及其父级与处理程序的创建者进行比较,假定您想要的是在单击菜单时关闭菜单,而真正想要的是在单击页面上的任意位置时关闭菜单。
  5. 侦听 body 元素上的事件会使代码更加脆弱。像这样无辜的造型会破坏它:body { margin-left:auto; margin-right: auto; width:960px;}

评论

3赞 zzzzBov 7/12/2016
“如果你点击菜单,或者离开菜单,它应该关闭,对吧?”通过拖动元素来取消单击仍会触发文档级单击,但目的不是继续关闭菜单。还有许多其他类型的对话框可以使用允许在内部单击的“单击”行为。
14赞 2 revs, 2 users 90%nazar kuliyev #14

我在一些jQuery日历插件中找到了这种方法。

function ClickOutsideCheck(e)
{
  var el = e.target;
  var popup = $('.popup:visible')[0];
  if (popup==undefined)
    return true;

  while (true){
    if (el == popup ) {
      return true;
    } else if (el == document) {
      $(".popup").hide();
      return false;
    } else {
      el = $(el).parent()[0];
    }
  }
};

$(document).bind('mousedown.popup', ClickOutsideCheck);
5赞 Rowan #15
$(document).click(function() {
    $(".overlay-window").hide();
});
$(".overlay-window").click(function() {
    return false;
});

如果单击文档,请隐藏给定的元素,除非单击该元素。

5赞 3 revs, 3 users 64%Satya Prakash #16

我在 YUI 3 中是这样做的:

// Detect the click anywhere other than the overlay element to close it.
Y.one(document).on('click', function (e) {
    if (e.target.ancestor('#overlay') === null && e.target.get('id') != 'show' && overlay.get('visible') == true) {
        overlay.hide();
    }
});

我正在检查祖先是否不是小部件元素容器,如果目标不是打开的小部件/元素,

如果我想关闭的小部件/元素已经打开(不是那么重要)。

27赞 benb #17

正如另一位发帖人所说,有很多陷阱,特别是如果你显示的元素(在本例中为菜单)具有交互式元素。 我发现以下方法相当强大:

$('#menuscontainer').click(function(event) {
    //your code that shows the menus fully

    //now set up an event listener so that clicking anywhere outside will close the menu
    $('html').click(function(event) {
        //check up the tree of the click target to check whether user has clicked outside of menu
        if ($(event.target).parents('#menuscontainer').length==0) {
            // your code to hide menu

            //this event listener has done its job so we can unbind it.
            $(this).unbind(event);
        }

    })
});
1赞 2 revs, 2 users 86%netdjw #18

这是我的代码:

// Listen to every click
$('html').click(function(event) {
    if ( $('#mypopupmenu').is(':visible') ) {
        if (event.target.id != 'click_this_to_show_mypopupmenu') {
            $('#mypopupmenu').hide();
        }
    }
});

// Listen to selector's clicks
$('#click_this_to_show_mypopupmenu').click(function() {

  // If the menu is visible, and you clicked the selector again we need to hide
  if ( $('#mypopupmenu').is(':visible') {
      $('#mypopupmenu').hide();
      return true;
  }

  // Else we need to show the popup menu
  $('#mypopupmenu').show();
});
3赞 Spencer Fry #19

这是我对这个问题的解决方案:

$(document).ready(function() {
  $('#user-toggle').click(function(e) {
    $('#user-nav').toggle();
    e.stopPropagation();
  });

  $('body').click(function() {
    $('#user-nav').hide(); 
  });

  $('#user-nav').click(function(e){
    e.stopPropagation();
  });
});
1赞 Toogy #20
jQuery().ready(function(){
    $('#nav').click(function (event) {
        $(this).addClass('activ');
        event.stopPropagation();
    });

    $('html').click(function () {
        if( $('#nav').hasClass('activ') ){
            $('#nav').removeClass('activ');
        }
    });
});
6赞 Salman A #21

在文档上挂接单击事件侦听器。在事件侦听器中,您可以查看事件对象,特别是 event.target,以查看单击了哪个元素:

$(document).click(function(e){
    if ($(e.target).closest("#menuscontainer").length == 0) {
        // .closest can help you determine if the element 
        // or one of its ancestors is #menuscontainer
        console.log("hide");
    }
});
33赞 2 revs, 2 users 76%srinath #22

这对我非常有效!

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});
2赞 3 revs, 2 users 91%Alexandre T. #23

老实说,我不喜欢以前的任何解决方案。

最好的方法是将“click”事件绑定到文档,并比较该单击是否真的在元素之外(就像 Art 在他的建议中所说的那样)。

但是,您会遇到一些问题:您将永远无法解除绑定它,并且您不能使用外部按钮来打开/关闭该元素。

这就是为什么我写了这个小插件(点击这里链接),以简化这些任务。可以更简单吗?

<a id='theButton' href="#">Toggle the menu</a><br/>
<div id='theMenu'>
    I should be toggled when the above menu is clicked,
    and hidden when user clicks outside.
</div>

<script>
$('#theButton').click(function(){
    $('#theMenu').slideDown();
});
$("#theMenu").dClickOutside({ ignoreList: $("#theButton") }, function(clickedObj){
    $(this).slideUp();
});
</script>
1赞 2 revs, 2 users 88%mlangenberg #24

只是一个警告,使用这个:

$('html').click(function() {
  // Hide the menus if visible
});

$('#menucontainer').click(function(event){
  event.stopPropagation();
});

它会阻止 Ruby on Rails UJS 驱动程序正常工作。例如,将不起作用。link_to 'click', '/url', :method => :delete

这可能是一种解决方法:

$('html').click(function() {
  // Hide the menus if visible
});

$('#menucontainer').click(function(event){
  if (!$(event.target).data('method')) {
    event.stopPropagation();
  }
});

评论

0赞 Peter Mortensen 1/23/2023
可能与此相关:为什么我需要 50 声望才能发表评论?我能做些什么?
4赞 3 revs, 2 users 73%Bilal Berjawi #25

这应该有效:

$('body').click(function (event) {
    var obj = $(event.target);
    obj = obj['context']; // context : clicked element inside body
    if ($(obj).attr('id') != "menuscontainer" && $('#menuscontainer').is(':visible') == true) {
        //hide menu
    }
});

评论

1赞 Mahesh Gaikwad 10/29/2014
$(“body > div:not(#dvid)”).click(function (e) { //你的代码 });
-4赞 constrictus #26
 <div class="feedbackCont" onblur="hidefeedback();">
        <div class="feedbackb" onclick="showfeedback();" ></div>
        <div class="feedbackhide" tabindex="1"> </div>
 </div>

function hidefeedback(){
    $j(".feedbackhide").hide();
}

function showfeedback(){
    $j(".feedbackhide").show();
    $j(".feedbackCont").attr("tabindex",1).focus();
}

这是我想出的最简单的解决方案。

评论

1赞 Sebastian Simon 4/6/2021
如果没有,那么它永远不会模糊;但如果是这样,那么如果它有焦点,它也会模糊,例如 正如预期的那样,但也在所有其他可聚焦的元素上。这是行不通的。<div class="feedbackCont">tabindex<div class="feedbackhide">
2赞 2 revs, 2 users 93%Tom #27

这里还有一种解决方案:

http://jsfiddle.net/zR76D/

用法:

<div onClick="$('#menu').toggle();$('#menu').clickOutside(function() { $(this).hide(); $(this).clickOutside('disable'); });">Open / Close Menu</div>
<div id="menu" style="display: none; border: 1px solid #000000; background: #660000;">I am a menu, whoa is me.</div>

插件:

(function($) {
    var clickOutsideElements = [];
    var clickListener = false;

    $.fn.clickOutside = function(options, ignoreFirstClick) {
        var that = this;
        if (ignoreFirstClick == null) ignoreFirstClick = true;

        if (options != "disable") {
            for (var i in clickOutsideElements) {
                if (clickOutsideElements[i].element[0] == $(this)[0]) return this;
            }

            clickOutsideElements.push({ element : this, clickDetected : ignoreFirstClick, fnc : (typeof(options) != "function") ? function() {} : options });

            $(this).on("click.clickOutside", function(event) {
                for (var i in clickOutsideElements) {
                    if (clickOutsideElements[i].element[0] == $(this)[0]) {
                        clickOutsideElements[i].clickDetected = true;
                    }
                }
            });

            if (!clickListener) {
                if (options != null && typeof(options) == "function") {
                    $('html').click(function() {
                        for (var i in clickOutsideElements) {
                            if (!clickOutsideElements[i].clickDetected) {
                                clickOutsideElements[i].fnc.call(that);
                            }
                            if (clickOutsideElements[i] != null) clickOutsideElements[i].clickDetected = false;
                        }
                    });
                    clickListener = true;
                }
            }
        }
        else {
            $(this).off("click.clickoutside");
            for (var i = 0; i < clickOutsideElements.length; ++i) {
                if (clickOutsideElements[i].element[0] == $(this)[0]) {
                    clickOutsideElements.splice(i, 1);
                }
            }
        }

        return this;
    }
})(jQuery);
2赞 maday #28

最广泛的方法是选择网页上的所有内容,除了您不希望检测到点击的元素之外,并在打开菜单时绑定点击事件。

然后,当菜单关闭时,删除绑定。

使用 .stopPropagation 可防止事件影响 menus容器的任何部分。

$("*").not($("#menuscontainer")).bind("click.OutsideMenus", function ()
{
    // hide the menus

    //then remove all of the handlers
    $("*").unbind(".OutsideMenus");
});

$("#menuscontainer").bind("click.OutsideMenus", function (event) 
{
    event.stopPropagation(); 
});
4赞 2 revs, 2 users 71%kboom #29

当只管理一个元素时,这里的解决方案可以正常工作。但是,如果有多个元素,问题就复杂得多。使用 e.stopPropagation() 和所有其他技巧将不起作用。

我想出了一个解决方案,也许不是那么容易,但总比没有好。看一看:

$view.on("click", function(e) {

    if(model.isActivated()) return;

        var watchUnclick = function() {
            rootView.one("mouseleave", function() {
                $(document).one("click", function() {
                    model.deactivate();
                });
                rootView.one("mouseenter", function() {
                    watchUnclick();
                });
            });
        };
        watchUnclick();
        model.activate();
    });

评论

0赞 Puerto AGP 12/1/2017
我认为 onmouseenter 和 onmouseleave 是解决方案,请 jsfiddle.net/1r73jm8m
8赞 mems #30

取而代之的是,使用流中断、模糊/聚焦事件或任何其他棘手的技术,只需将事件流与元素的亲缘关系相匹配:

$(document).on("click.menu-outside", function(event){
    // Test if target and it's parent aren't #menuscontainer
    // That means the click event occur on other branch of document tree
    if(!$(event.target).parents().andSelf().is("#menuscontainer")){
        // Click outisde #menuscontainer
        // Hide the menus (but test if menus aren't already hidden)
    }
});

要删除单击外部事件侦听器,只需:

$(document).off("click.menu-outside");
3赞 3 revs, 2 users 94%Anders #31

标记为已接受答案的答案没有考虑到您可以在元素上覆盖,例如对话框、弹出框、日期选择器等。点击这些内容不应隐藏元素。

我制作了自己的版本,确实考虑到了这一点。它是作为 KnockoutJS 绑定创建的,但它可以很容易地转换为仅 jQuery。

它通过对所有具有 z 索引或绝对位置的可见元素进行第一次查询来工作。然后,如果单击外部,它会针对我想要隐藏的元素测试这些元素。如果是命中,我会计算一个新的绑定矩形,该矩形考虑了覆盖边界。

ko.bindingHandlers.clickedIn = (function () {
    function getBounds(element) {
        var pos = element.offset();
        return {
            x: pos.left,
            x2: pos.left + element.outerWidth(),
            y: pos.top,
            y2: pos.top + element.outerHeight()
        };
    }

    function hitTest(o, l) {
        function getOffset(o) {
            for (var r = { l: o.offsetLeft, t: o.offsetTop, r: o.offsetWidth, b: o.offsetHeight };
                o = o.offsetParent; r.l += o.offsetLeft, r.t += o.offsetTop);
            return r.r += r.l, r.b += r.t, r;
        }

        for (var b, s, r = [], a = getOffset(o), j = isNaN(l.length), i = (j ? l = [l] : l).length; i;
            b = getOffset(l[--i]), (a.l == b.l || (a.l > b.l ? a.l <= b.r : b.l <= a.r))
                && (a.t == b.t || (a.t > b.t ? a.t <= b.b : b.t <= a.b)) && (r[r.length] = l[i]));
        return j ? !!r.length : r;
    }

    return {
        init: function (element, valueAccessor) {
            var target = valueAccessor();
            $(document).click(function (e) {
                if (element._clickedInElementShowing === false && target()) {
                    var $element = $(element);
                    var bounds = getBounds($element);

                    var possibleOverlays = $("[style*=z-index],[style*=absolute]").not(":hidden");
                    $.each(possibleOverlays, function () {
                        if (hitTest(element, this)) {
                            var b = getBounds($(this));
                            bounds.x = Math.min(bounds.x, b.x);
                            bounds.x2 = Math.max(bounds.x2, b.x2);
                            bounds.y = Math.min(bounds.y, b.y);
                            bounds.y2 = Math.max(bounds.y2, b.y2);
                        }
                    });

                    if (e.clientX < bounds.x || e.clientX > bounds.x2 ||
                        e.clientY < bounds.y || e.clientY > bounds.y2) {

                        target(false);
                    }
                }
                element._clickedInElementShowing = false;
            });

            $(element).click(function (e) {
                e.stopPropagation();
            });
        },
        update: function (element, valueAccessor) {
            var showing = ko.utils.unwrapObservable(valueAccessor());
            if (showing) {
                element._clickedInElementShowing = true;
            }
        }
    };
})();
1赞 2 revsRoei Bahumi #32

这是一个更通用的解决方案,允许监视多个元素,并在队列中动态添加和删除元素

它包含一个全局队列 (autoCloseQueue) - 一个对象容器,用于在外部单击时关闭的元素。

每个队列对象键都应该是 DOM 元素 id,值应该是具有 2 个回调函数的对象:

 {onPress: someCallbackFunction, onOutsidePress: anotherCallbackFunction}

把它放在你的文档就绪回调中:

window.autoCloseQueue = {}  

$(document).click(function(event) {
    for (id in autoCloseQueue){
        var element = autoCloseQueue[id];
        if ( ($(e.target).parents('#' + id).length) > 0) { // This is a click on the element (or its child element)
            console.log('This is a click on an element (or its child element) with  id: ' + id);
            if (typeof element.onPress == 'function') element.onPress(event, id);
        } else { //This is a click outside the element
            console.log('This is a click outside the element with id: ' + id);
            if (typeof element.onOutsidePress == 'function') element.onOutsidePress(event, id); //call the outside callback
            delete autoCloseQueue[id]; //remove the element from the queue
        }
    }
});

然后,当创建 id 为“menuscontainer”的 DOM 元素时,只需将此对象添加到队列中:

window.autoCloseQueue['menuscontainer'] = {onOutsidePress: clickOutsideThisElement}
4赞 Webmaster G #33

我最终做了这样的事情:

$(document).on('click', 'body, #msg_count_results .close',function() {
    $(document).find('#msg_count_results').remove();
});
$(document).on('click','#msg_count_results',function(e) {
    e.preventDefault();
    return false;
});

我在新容器中有一个关闭按钮,用于最终用户友好的 UI。我不得不使用 return false 才能不通过。当然,有一个 A HREF 可以带你去某个地方会很好,或者你可以打电话给一些 ajax 的东西。无论哪种方式,它对我来说都很好用。正是我想要的。

-4赞 3 revs, 3 users 52%lan.ta #34

试试这个代码:

if ($(event.target).parents().index($('#searchFormEdit')) == -1 &&
    $(event.target).parents().index($('.DynarchCalendar-topCont')) == -1 &&
    (_x < os.left || _x > (os.left + 570) || _y < os.top || _y > (os.top + 155)) &&
    isShowEditForm) {

    setVisibleEditForm(false);
}
-4赞 3 revs, 2 users 87%Yacine Zalouani #35

您可以将 tabindex 设置为 DOM 元素。当用户在 DOM 元素外部单击时,这将触发模糊事件。

演示

<div tabindex="1">
    Focus me
</div>

document.querySelector("div").onblur = function(){
   console.log('clicked outside')
}
document.querySelector("div").onfocus = function(){
   console.log('clicked inside')
}

评论

0赞 Sebastian Simon 4/6/2021
tabindex 破坏了可访问性;有比这个 hack 更好的选择,所以不值得将其用于此目的。
0赞 2 revs, 2 users 69%Awena #36

这将在您单击元素时切换导航菜单。

$(document).on('click', function(e) {
  var elem = $(e.target).closest('#menu'),
    box = $(e.target).closest('#nav');
  if (elem.length) {
    e.preventDefault();
    $('#nav').toggle();
  } else if (!box.length) {
    $('#nav').hide();
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<li id="menu">
  <a></a>
</li>
<ul id="nav">
  <!--Nav will toggle when you Click on Menu(it can be an icon in this example)-->
  <li class="page"><a>Page1</a></li>
  <li class="page"><a>Page2</a></li>
  <li class="page"><a>Page3</a></li>
  <li class="page"><a>Page4</a></li>
</ul>

-4赞 3 revs, 2 users 73%Peter Rader #37

标准 HTML:

将菜单包围起来,并获取焦点状态更改。<label>

http://jsfiddle.net/bK3gL/

另外:您可以通过以下方式展开菜单。Tab

评论

0赞 Grim 6/10/2014
另外:您可以通过 <kbd>tab</kbd 展开菜单>
1赞 Sebastian Simon 4/6/2021
这是无效的 HTML。
-1赞 2 revsKyleMit #38

作为 Art 这个伟大答案的包装,并使用 OP 最初请求的语法,这里有一个 jQuery 扩展,它可以记录在设置元素之外是否发生了点击。

$.fn.clickOutsideThisElement = function (callback) {
    return this.each(function () {
        var self = this;
        $(document).click(function (e) {
            if (!$(e.target).closest(self).length) {
                callback.call(self, e)
            }
        })
    });
};

然后你可以这样调用:

$("#menuscontainer").clickOutsideThisElement(function() {
    // handle menu toggle
});

这是一个小提琴演示

16赞 2 revs, 2 users 76%Bohdan Lyzanets #39

作为变体:

var $menu = $('#menucontainer');
$(document).on('click', function (e) {

    // If element is opened and click target is outside it, hide it
    if ($menu.is(':visible') && !$menu.is(e.target) && !$menu.has(e.target).length) {
        $menu.hide();
    }
});

它在停止事件传播方面没有问题,并且更好地支持同一页面上的多个菜单,其中在第一个菜单打开时单击第二个菜单将使第一个菜单在 stopPropagation 解决方案中保持打开状态。

2赞 2 revs, 2 users 80%Manish Shrivastava #40

对于 iPad 和 iPhone 等触摸设备,我们可以使用以下代码:

$(document).on('touchstart', function (event) {
    var container = $("YOUR CONTAINER SELECTOR");

    if (!container.is(e.target) &&            // If the target of the click isn't the container...
        container.has(e.target).length === 0) // ... nor a descendant of the container
    {
        container.hide();
    }
});
-4赞 3 revs, 3 users 67%shiv #41

使用 not():

$("#id").not().click(function() {
    alert('Clicked other that #id');
});

评论

6赞 flu 12/5/2014
这是行不通的。 从选定元素列表中删除元素 (api.jquery.com/not)。如果没有选择器作为参数,它什么都不做,因此返回与我们试图完成的任务完全相反。not()$('#id')
-4赞 2 revs, 2 users 60%Samsquanch #42
$("body > div:not(#dvid)").click(function (e) {
    //your code
}); 

评论

3赞 bguiz 10/29/2014
向其他元素添加一个点击处理程序?你会扼杀性能。有更好的方法可以做到这一点。
3赞 Migio B 3/1/2015
这将向除 #dvid 以外的所有 div 元素添加一个单击处理程序,这对页面来说非常昂贵,如果您内部有一些 div,#divid 它们也有处理程序,这可能是一个意想不到的效果
-4赞 aroykos #43
$("html").click(function(){
    if($('#info').css("opacity")>0.9) {
        $('#info').fadeOut('fast');
    }
});

评论

1赞 Sebastian Simon 4/6/2021
这似乎与问题无关。
23赞 3 revs, 2 users 73%Iman Sedighi #44

解决方案1

与其使用 event.stopPropagation() 可能会产生一些副作用,只需定义一个简单的标志变量并添加一个条件即可。我对此进行了测试并正常工作,没有对 stopPropagation 产生任何副作用:if

var flag = "1";
$('#menucontainer').click(function(event){
    flag = "0"; // flag 0 means click happened in the area where we should not do any action
});

$('html').click(function() {
    if(flag != "0"){
        // Hide the menus if visible
    }
    else {
        flag = "1";
    }
});

解决方案2

只需一个简单的条件:if

$(document).on('click', function(event){
    var container = $("#menucontainer");
    if (!container.is(event.target) &&            // If the target of the click isn't the container...
        container.has(event.target).length === 0) // ... nor a descendant of the container
    {
        // Do whatever you want to do when click is outside the element
    }
});

评论

0赞 Migio B 3/1/2015
我将这个解决方案与布尔标志一起使用,它也适用于铰接式 DOm,如果内部 #menucontainer 还有很多其他元素
0赞 Alice 2/18/2020
解决方案 1 效果更好,因为它可以处理在事件传播到文档时从 DOM 中删除单击目标的情况。
13赞 7 revs, 2 users 97%user4639281 #45

这是面向未来观众的普通 JavaScript 解决方案。

单击文档中的任何元素时,如果单击的元素的 ID 已切换,或者隐藏的元素未隐藏,并且隐藏的元素不包含单击的元素,则切换该元素。

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();
<a href="javascript:void(0)" id="toggle">Toggle Hidden Div</a>
<div id="hidden" style="display: none;">This content is normally hidden. click anywhere other than this content to make me disappear</div>

如果要在同一页面上进行多个切换,则可以使用如下操作:

  1. 将类名添加到可折叠项。hidden
  2. 单击文档后,关闭所有不包含单击元素且未隐藏的隐藏元素
  3. 如果单击的元素是切换,则切换指定的元素。

(function () {
    "use strict";
    var hiddenItems = document.getElementsByClassName('hidden'), hidden;
    document.addEventListener('click', function (e) {
        for (var i = 0; hidden = hiddenItems[i]; i++) {
            if (!hidden.contains(e.target) && hidden.style.display != 'none')
                hidden.style.display = 'none';
        }
        if (e.target.getAttribute('data-toggle')) {
            var toggle = document.querySelector(e.target.getAttribute('data-toggle'));
            toggle.style.display = toggle.style.display == 'none' ? 'block' : 'none';
        }
    }, false);
})();
<a href="javascript:void(0)" data-toggle="#hidden1">Toggle Hidden Div</a>
<div class="hidden" id="hidden1" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden2">Toggle Hidden Div</a>
<div class="hidden" id="hidden2" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden3">Toggle Hidden Div</a>
<div class="hidden" id="hidden3" style="display: none;" data-hidden="true">This content is normally hidden</div>

5赞 Scott Richardson #46

我们实施了一个解决方案,部分基于上面用户的评论,这对我们来说非常有效。当点击这些元素之外时,我们使用它来隐藏搜索框/结果,不包括原来的元素。

// HIDE SEARCH BOX IF CLICKING OUTSIDE
$(document).click(function(event){ 
    // IF NOT CLICKING THE SEARCH BOX OR ITS CONTENTS OR SEARCH ICON 
    if ($("#search-holder").is(":visible") && !$(event.target).is("#search-holder *, #search")) {
        $("#search-holder").fadeOut('fast');
        $("#search").removeClass('active');
    }
});

它首先检查搜索框是否已经可见,在我们的例子中,它还删除了隐藏/显示搜索按钮上的活动类。

5赞 bbe #47

为最受欢迎的答案投票,但添加

&& (e.target != $('html').get(0)) // ignore the scrollbar

因此,单击滚动条不会[隐藏或其他]您的目标元素。

评论

0赞 Peter Mortensen 1/23/2023
相关新闻: 为什么我需要 50 声望才能发表评论?我能做些什么?
-3赞 martinedwards #48

这是一个经典的案例,其中对 HTML 进行调整将是一个更好的解决方案。为什么不设置对不包含菜单项的元素的单击?然后,您无需添加传播。

$('.header, .footer, .main-content').click(function() {
//Hide the menus if visible
});

评论

2赞 Marcio 10/21/2015
这样做是个坏主意,因为如果以后您决定添加某种 footer2 或其他介于两者之间的东西,您也必须记住添加它。$('html').click() 更容易,并使其在任何情况下都能工作。
0赞 6 revsDaniel Tonon #49

外部点击插件!

用法:

$('.target-element').outsideClick(function(event){
    //code that fires when user clicks outside the element
    //event = the click event
    //$(this) = the '.target-element' that is firing this function 
}, '.excluded-element')

它的代码:

(function($) {

//when the user hits the escape key, it will trigger all outsideClick functions
$(document).on("keyup", function (e) {
    if (e.which == 27) $('body').click(); //escape key
});

//The actual plugin
$.fn.outsideClick = function(callback, exclusions) {
    var subject = this;

    //test if exclusions have been set
    var hasExclusions = typeof exclusions !== 'undefined';

    //switches click event with touch event if on a touch device
    var ClickOrTouchEvent = "ontouchend" in document ? "touchend" : "click";

    $('body').on(ClickOrTouchEvent, function(event) {
        //click target does not contain subject as a parent
        var clickedOutside = !$(event.target).closest(subject).length;

        //click target was on one of the excluded elements
        var clickedExclusion = $(event.target).closest(exclusions).length;

        var testSuccessful;

        if (hasExclusions) {
            testSuccessful = clickedOutside && !clickedExclusion;
        } else {
            testSuccessful = clickedOutside;
        }

        if(testSuccessful) {
            callback.call(subject, event);
        }
    });

    return this;
};

}(jQuery));

改编自此答案 https://stackoverflow.com/a/3028037/1611058

评论

0赞 Sebastian Simon 4/6/2021
请不要这样做。有些设备同时具有单击和触摸功能,但您只绑定了一个设备。ClickOrTouchEvent
60赞 4 revs, 3 users 73%Rameez Rami #50

经过研究,我找到了三种可行的解决方案

第一个解决方案

<script>
    //The good thing about this solution is it doesn't stop event propagation.

    var clickFlag = 0;
    $('body').on('click', function () {
        if(clickFlag == 0) {
            console.log('hide element here');
            /* Hide element here */
        }
        else {
            clickFlag=0;
        }
    });
    $('body').on('click','#testDiv', function (event) {
        clickFlag = 1;
        console.log('showed the element');
        /* Show the element */
    });
</script>

第二种解决方案

<script>
    $('body').on('click', function(e) {
        if($(e.target).closest('#testDiv').length == 0) {
           /* Hide dropdown here */
        }
    });
</script>

第三种解决方案

<script>
    var specifiedElement = document.getElementById('testDiv');
    document.addEventListener('click', function(event) {
        var isClickInside = specifiedElement.contains(event.target);
        if (isClickInside) {
          console.log('You clicked inside')
        }
        else {
          console.log('You clicked outside')
        }
    });
</script>

评论

13赞 dbarth 7/27/2016
第三种解决方案是迄今为止最优雅的检查方式。它也不涉及jQuery的任何开销。很好。它帮了大忙。谢谢。
0赞 Peter Mortensen 1/23/2023
回复“我忘记了页面链接以供参考”:我以前从未见过抄袭者的借口。
0赞 Rameez Rami 1/23/2023
@PeterMortensen我不明白。这里有什么可抄袭的?我没有在这里发布博客文章或同上代码来抄袭......无论如何,为了您的高兴,我已经删除了那行:)
-2赞 Nitekurs #51
    $('#menucontainer').click(function(e){
        e.stopPropagation();
     });

    $(document).on('click',  function(e){
        // code
    });

评论

1赞 Sebastian Simon 4/6/2021
除了公认的答案已经提供的内容之外,没有增加太多内容。
26赞 4 revs, 3 users 70%Jitendra Damor #52

这种情况的简单解决方案是:

$(document).mouseup(function (e)
{
    var container = $("YOUR SELECTOR"); // Give you class or ID
    
    if (!container.is(e.target) &&            // If the target of the click is not the desired div or section
        container.has(e.target).length === 0) // ... nor a descendant-child of the container
    {
        container.hide();
    }
});

上面的脚本将隐藏 if outside of the click 事件被触发。divdiv

-1赞 wsc #53

$('html').click(function() {
//Hide the menus if visible
});

$('#menucontainer').click(function(event){
    event.stopPropagation();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
 <button id='#menucontainer'>Ok</button> 
</html>

评论

1赞 Sebastian Simon 4/6/2021
这只是在撰写本文时对已接受答案的修订版的逐字副本。除了一个被误解的 HTML 示例外,这不会添加任何内容:应该是包含整个菜单的元素;并且没有实现,所以没有实际的演示。#menucontainer//Hide the menus if visible
-1赞 Yishu Fang #54

试试这个:

$('html').click(function(e) {
  if($(e.target).parents('#menuscontainer').length == 0) {
    $('#menuscontainer').hide();
  }
});

https://jsfiddle.net/4cj4jxy0/

但请注意,如果点击事件无法到达代码,则此操作不起作用。(也许其他元素有)。htmlstopPropagation()

0赞 Qwertiy #55

订阅点击捕获阶段,以处理对调用 .
使用其他名称在文档元素上重新触发它。
preventDefaultclick-anywhere

document.addEventListener('click', function (event) {
  event = $.event.fix(event);
  event.type = 'click-anywhere';
  $document.trigger(event);
}, true);

然后,在您需要单击外部功能的地方,订阅事件并检查单击是否在您感兴趣的元素之外:click-anywheredocument

$(document).on('click-anywhere', function (event) {
  if (!$(event.target).closest('#smth').length) {
    // Do anything you need here
  }
});

一些注意事项:

  • 您必须使用,因为在发生单击的所有元素上触发事件将是一个性能错误。document

  • 这个功能可以包装成特殊的插件,在外部点击时会调用一些回调。

  • 您不能使用 jQuery 本身订阅捕获阶段。

  • 您不需要文档加载来订阅,因为订阅处于开启状态,甚至不需要在其 上,因此它始终独立存在脚本放置和加载状态。documentbody

评论

0赞 Nate Whittaker 6/17/2016
+1 提及相位 -- 当然可以降低杀死东西的风险(取决于你把听众连接在哪里)。capturestopPropagation()
5赞 2 revsMatt Goodwin #56

为了更易于使用和更具表现力的代码,我为此创建了一个jQuery插件:

$('div.my-element').clickOut(function(target) { 
    //do something here... 
});

注意:target 是用户实际点击的元素。但是回调仍然在原始元素的上下文中执行,因此您可以像在 jQuery 回调中所期望的那样使用它。

插件:

$.fn.clickOut = function (parent, fn) {
    var context = this;
    fn = (typeof parent === 'function') ? parent : fn;
    parent = (parent instanceof jQuery) ? parent : $(document);

    context.each(function () {
        var that = this;
        parent.on('click', function (e) {
            var clicked = $(e.target);
            if (!clicked.is(that) && !clicked.parents().is(that)) {
                if (typeof fn === 'function') {
                    fn.call(that, clicked);
                }
            }
        });

    });
    return context;
};

默认情况下,单击事件侦听器放置在文档上。但是,如果要限制事件侦听器范围,可以传入一个 jQuery 对象,该对象表示父级元素,该元素将是侦听单击的顶部父级元素。这样可以防止不必要的文档级事件侦听器。显然,除非提供的父元素是初始元素的父元素,否则它将不起作用。

像这样使用:

$('div.my-element').clickOut($('div.my-parent'), function(target) { 
    //do something here...
});
-1赞 FDisk #57
$(document).on('click.menu.hide', function(e){
  if ( !$(e.target).closest('#my_menu').length ) {
    $('#my_menu').find('ul').toggleClass('active', false);
  }
});

$(document).on('click.menu.show', '#my_menu li', function(e){
  $(this).find('ul').toggleClass('active');
});
div {
  float: left;
}

ul {
  padding: 0;
  position: relative;
}
ul li {
  padding: 5px 25px 5px 10px;
  border: 1px solid silver;
  cursor: pointer;
  list-style: none;
  margin-top: -1px;
  white-space: nowrap;
}
ul li ul:before {
  margin-right: -20px;
  position: absolute;
  top: -17px;
  right: 0;
  content: "\25BC";
}
ul li ul li {
  visibility: hidden;
  height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0 0 1px 0;
}
ul li ul li:last-child {
  border: none;
}
ul li ul.active:before {
  content: "\25B2";
}
ul li ul.active li {
  display: list-item;
  visibility: visible;
  height: inherit;
  padding: 5px 25px 5px 10px;
}
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<div>
  <ul id="my_menu">
    <li>Menu 1
      <ul>
        <li>subMenu 1</li>
        <li>subMenu 2</li>
        <li>subMenu 3</li>
        <li>subMenu 4</li>
      </ul>
    </li>
    <li>Menu 2
      <ul>
        <li>subMenu 1</li>
        <li>subMenu 2</li>
        <li>subMenu 3</li>
        <li>subMenu 4</li>
      </ul>
    </li>
    <li>Menu 3</li>
    <li>Menu 4</li>
    <li>Menu 5</li>
    <li>Menu 6</li>
  </ul>
</div>

这是 jsbin 版本 http://jsbin.com/xopacadeni/edit?html,css,js,output

评论

0赞 Peter Mortensen 2/8/2023
这不是 Stack Overflow 的用途。解释对于一个好的答案至关重要。”
0赞 FDisk 2/9/2023
链接几乎与答案😅相同
425赞 7 revszzzzBov #58

如何检测元素外部的点击?

这个问题之所以如此受欢迎,并且有如此多的答案,是因为它看似复杂。经过将近八年的时间和数十个答案,我真的很惊讶地发现对可访问性的关注如此之少。

我想在用户单击菜单区域之外时隐藏这些元素。

这是一项崇高的事业,也是实际问题。这个问题的标题——这似乎是大多数答案试图解决的问题——包含了一个不幸的红鲱鱼。

提示:这是“点击”这个词!

您实际上并不想绑定单击处理程序。

如果绑定单击处理程序以关闭对话框,则已失败。你失败的原因是不是每个人都会触发事件。不使用鼠标的用户将能够通过按 来转出您的对话框(您的弹出菜单可以说是一种对话框),然后他们将无法读取对话框后面的内容,而不会随后触发事件。clickTabclick

因此,让我们重新表述这个问题。

当用户完成对话时,如何关闭对话?

这就是我们的目标。不幸的是,现在我们需要绑定事件,而绑定并不那么简单。userisfinishedwiththedialog

那么,我们如何检测用户是否已经完成了对对话的使用呢?

focusout事件

一个好的开始是确定焦点是否已离开对话框。

提示:小心模糊事件,如果事件绑定到冒泡阶段,则模糊不会传播!

jQuery的focusout就可以了。如果不能使用 jQuery,则可以在捕获阶段使用:blur

element.addEventListener('blur', ..., true);
//                       use capture: ^^^^

此外,对于许多对话框,需要允许容器获得焦点。添加以允许对话框动态接收焦点,而不会中断 Tab 键流。tabindex="-1"

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on('focusout', function () {
  $(this).removeClass('active');
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


如果您玩该演示超过一分钟,您应该很快就会开始看到问题。

首先是对话框中的链接不可点击。尝试单击它或按 Tab 键切换到它将导致对话框在交互发生之前关闭。这是因为在再次触发事件之前,聚焦内部元素会触发事件。focusoutfocusin

解决方法是在事件循环上对状态更改进行排队。这可以通过使用 来完成,也可以用于不支持 的浏览器。一旦排队,它可以通过后续取消:setImmediate(...)setTimeout(..., 0)setImmediatefocusin

$('.submenu').on({
  focusout: function (e) {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function (e) {
    clearTimeout($(this).data('submenuTimer'));
  }
});

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>

第二个问题是,再次按下链接时,对话框不会关闭。这是因为对话框失去焦点,触发关闭行为,之后单击链接会触发对话框重新打开。

与上一个问题类似,需要对焦点状态进行管理。鉴于状态更改已排队,只需在对话触发器上处理焦点事件即可:

这应该看起来很熟悉
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


Esc钥匙

如果您认为处理焦点状态已经完成,那么您可以采取更多措施来简化用户体验。

这通常是一个“很高兴拥有”的功能,但通常当您拥有任何类型的模态或弹出窗口时,键会将其关闭。Esc

keydown: function (e) {
  if (e.which === 27) {
    $(this).removeClass('active');
    e.preventDefault();
  }
}

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('active');
      e.preventDefault();
    }
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


如果您知道对话框中有可聚焦的元素,则无需直接聚焦对话框。如果您正在构建菜单,则可以将重点放在第一个菜单项上。

click: function (e) {
  $(this.hash)
    .toggleClass('submenu--active')
    .find('a:first')
    .focus();
  e.preventDefault();
}

$('.menu__link').on({
  click: function (e) {
    $(this.hash)
      .toggleClass('submenu--active')
      .find('a:first')
      .focus();
    e.preventDefault();
  },
  focusout: function () {
    $(this.hash).data('submenuTimer', setTimeout(function () {
      $(this.hash).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('submenuTimer'));  
  }
});

$('.submenu').on({
  focusout: function () {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('submenuTimer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('submenu--active');
      e.preventDefault();
    }
  }
});
.menu {
  list-style: none;
  margin: 0;
  padding: 0;
}
.menu:after {
  clear: both;
  content: '';
  display: table;
}
.menu__item {
  float: left;
  position: relative;
}

.menu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}
.menu__link:hover,
.menu__link:focus {
  background-color: black;
  color: lightblue;
}

.submenu {
  border: 1px solid black;
  display: none;
  left: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  position: absolute;
  top: 100%;
}
.submenu--active {
  display: block;
}

.submenu__item {
  width: 150px;
}

.submenu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}

.submenu__link:hover,
.submenu__link:focus {
  background-color: black;
  color: lightblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="menu">
  <li class="menu__item">
    <a class="menu__link" href="#menu-1">Menu 1</a>
    <ul class="submenu" id="menu-1" tabindex="-1">
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
  <li class="menu__item">
    <a  class="menu__link" href="#menu-2">Menu 2</a>
    <ul class="submenu" id="menu-2" tabindex="-1">
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
</ul>
lorem ipsum <a href="http://example.com/">dolor</a> sit amet.


WAI-ARIA 角色和其他辅助功能支持

这个答案希望涵盖了此功能的可访问键盘和鼠标支持的基础知识,但由于它已经相当大了,我将避免对 WAI-ARIA 角色和属性进行任何讨论,但我强烈建议实现者参考规范,了解他们应该使用哪些角色和任何其他适当的属性的详细信息。

评论

47赞 Cyrille 10/29/2016
这是最完整的答案,并考虑了解释和可访问性。我认为这应该是公认的答案,因为大多数其他答案只处理点击,只是代码片段被丢弃,没有任何解释。
0赞 ICW 10/14/2021
You don't actually want to bind click handlers.您可以绑定单击处理程序,还可以处理用户没有鼠标的情况。它不会损害可访问性,它只是为使用鼠标的用户添加功能。向一组用户添加功能不会伤害无法使用该功能的用户。您可以提供多种关闭 diablog 的方法 这实际上是一个非常常见的逻辑谬误。为一组用户提供功能是完全可以的,即使其他用户没有受益。我同意所有用户都应该能够获得良好的体验
1赞 zzzzBov 10/14/2021
@ICW,通过使用 OR 处理程序,您仍将完全支持鼠标和触摸用户,并且它还具有支持键盘用户的额外好处。我从来没有建议你不应该支持鼠标用户。blurfocusout
1赞 Merchako 2/16/2023
看看这在 vanilla JS 中是什么样子会非常有帮助。我知道这个问题在 JQuery 中,但 14 年过去了,JQuery 的答案占据了主导地位,以至于很难学习到最新的解决方案。
0赞 2 revs, 2 users 62%Thamaraiselvam #59

要在单击外部时隐藏:fileTreeClass

jQuery(document).mouseup(function (e) {
    var container = $(".fileTreeClass");
    if (!container.is(e.target) && // If the target of the click isn't the container...
         container.has(e.target).length === 0) // ... nor a descendant of the container
    {
        container.hide();
    }
});
1赞 froilanq #60

简单插件:

$.fn.clickOff = function(callback, selfDestroy) {
    var clicked = false;
    var parent = this;
    var destroy = selfDestroy || true;

    parent.click(function() {
        clicked = true;
    });

    $(document).click(function(event) {
        if (!clicked && parent.is(':visible')) {
            if(callback) callback.call(parent, event)
        }
        if (destroy) {
            //parent.clickOff = function() {};
            //parent.off("click");
            //$(document).off("click");
            parent.off("clickOff");
        }
        clicked = false;
    });
};

用:

$("#myDiv").clickOff(function() {
   alert('clickOff');
});

评论

0赞 Peter Mortensen 2/8/2023
这不是 Stack Overflow 的用途。解释对于一个好的答案至关重要。”
2赞 2 revs, 2 users 60%lowtechsun #61

对于某些人来说,这可能是一个更好的解决方案。

$(".menu_link").click(function(){
    // show menu code
});

$(".menu_link").mouseleave(function(){
    //hide menu code, you may add a timer for 3 seconds before code to be run
});

我知道mouseleave不仅意味着在外面点击,还意味着离开该元素的区域。

一旦菜单本身位于元素内部,那么单击或继续移动菜单本身应该不会有问题。menu_link

评论

0赞 Puerto AGP 12/1/2017
mouseleave 和某种 hack 可能会为某些人解决它,这里有一个测试 jsfiddle.net/1r73jm8m
2赞 Waltur Buerk #62

我相信最好的方法就是这样。

$(document).on("click", function(event) {
  clickedtarget = $(event.target).closest('#menuscontainer');
  $("#menuscontainer").not(clickedtarget).hide();
});

这种类型的解决方案可以很容易地适用于多个菜单以及通过 javascript 动态添加的菜单。基本上,它只允许您单击文档中的任意位置,并检查您单击的元素,然后选择最接近的“#menuscontainer”。然后,它会隐藏所有菜单容器,但会排除您单击的容器。

不确定您的菜单是如何构建的,但请随时在 JSFiddle 中复制我的代码。这是一个非常简单但功能齐全的菜单/模式系统。您需要做的就是构建 html 菜单,代码将为您完成工作。

https://jsfiddle.net/zs6anrn7/

3赞 Lucas #63

我知道这个问题有一百万个答案,但我一直喜欢使用 HTML 和 CSS 来完成大部分工作。在本例中,z-index 和定位。我发现的最简单的方法如下:

$("#show-trigger").click(function(){
  $("#element").animate({width: 'toggle'});
  $("#outside-element").show();
});
$("#outside-element").click(function(){
  $("#element").hide();
  $("#outside-element").hide();
});
#outside-element {
  position:fixed;
  width:100%;
  height:100%;
  z-index:1;
  display:none;
}
#element {
  display:none;
  padding:20px;
  background-color:#ccc;
  width:300px;
  z-index:2;
  position:relative;
}
#show-trigger {
  padding:20px;
  background-color:#ccc;
  margin:20px auto;
  z-index:2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="outside-element"></div>
<div id="element">
  <div class="menu-item"><a href="#1">Menu Item 1</a></div>
  <div class="menu-item"><a href="#2">Menu Item 1</a></div>
  <div class="menu-item"><a href="#3">Menu Item 1</a></div>
  <div class="menu-item"><a href="#4">Menu Item 1</a></div>
</div>
<div id="show-trigger">Show Menu</div>

这创造了一个安全的环境,因为除非菜单实际打开,否则不会触发任何内容,并且 z-index 可以保护元素中的任何内容在单击时不会产生任何失火。

此外,您不需要 jQuery 通过传播调用覆盖所有基础,也不必清除所有内部元素的失火。

2赞 2 revs, 2 users 60%Karthikeyan Ganesan #64
$(document).on("click", function (event)
{
  console.log(event);
  if ($(event.target).closest('.element').length == 0)
  {
    // Your code here
    if ($(".element").hasClass("active"))
    {
      $(".element").removeClass("active");
    }
  }
});

尝试使用此编码来获取解决方案。

17赞 5 revs, 3 users 96%Dan Philip Bejoy #65

该事件具有一个名为 event.path 的元素属性,该属性是“按树顺序排列的所有祖先的静态有序列表”。要检查事件是否源自特定 DOM 元素或其子元素之一,只需检查该特定 DOM 元素的路径即可。它还可用于通过逻辑地检查函数中的元素来检查多个元素。ORsome

$("body").click(function() {
  target = document.getElementById("main");
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  })
  if (flag) {
    console.log("Inside")
  } else {
    console.log("Outside")
  }
});
#main {
  display: inline-block;
  background: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="main">
  <ul>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
  </ul>
</div>
<div id="main2">
  Outside Main
</div>

所以就你的情况来说,它应该是

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});
8赞 2 revs, 2 users 69%Walt #66

如果有人好奇,这里有一个 JavaScript 解决方案(ES6):

window.addEventListener('mouseup', e => {
    if (e.target != yourDiv && e.target.parentNode != yourDiv) {
        yourDiv.classList.remove('show-menu');
        // Or yourDiv.style.display = 'none';
    }
})

还有 ES5,以防万一:

window.addEventListener('mouseup', function (e) {
    if (e.target != yourDiv && e.target.parentNode != yourDiv) {
        yourDiv.classList.remove('show-menu');
        // Or yourDiv.style.display = 'none';
    }
});
3赞 2 revsFabian #67

这是我为解决问题所做的工作。

$(window).click(function (event) {
    //To improve performance add a checklike 
    //if(myElement.isClosed) return;
    var isClickedElementChildOfMyBox = isChildOfElement(event,'#id-of-my-element');

    if (isClickedElementChildOfMyBox)
        return;

    //your code to hide the element 
});

var isChildOfElement = function (event, selector) {
    if (event.originalEvent.path) {
        return event.originalEvent.path[0].closest(selector) !== null;
    }

    return event.originalEvent.originalTarget.closest(selector) !== null;
}
2赞 hienbt88 #68

这对我有用

$("body").mouseup(function(e) {
    var subject = $(".main-menu");
    if(e.target.id != subject.attr('id') && !subject.has(e.target).length) {
        $('.sub-menu').hide();
    }
});
6赞 Duannx #69

这是纯javascript的简单解决方案。它与 ES6 是最新的

var isMenuClick = false;
var menu = document.getElementById('menuscontainer');
document.addEventListener('click',()=>{
    if(!isMenuClick){
       //Hide the menu here
    }
    //Reset isMenuClick 
    isMenuClick = false;
})
menu.addEventListener('click',()=>{
    isMenuClick = true;
})

评论

0赞 MortenMoulder 11/17/2017
“与 ES6 保持同步”是一个非常大胆的主张,而 ES6 唯一最新的就是做而不是 .你所拥有的被归类为带有 ES6 的纯 JavaScript。() => {}function() {}
0赞 Duannx 11/18/2017
@MortenMoulder:是的。它只是为了引起注意,即使它实际上是 ES6。但只要看看解决方案。我认为这很好。
0赞 Alice 2/18/2020
它是普通的 JS,适用于从 DOM 中删除的事件目标(例如,当选择内部弹出窗口的值时,立即关闭弹出窗口)。+1 来自我!
0赞 5 revs, 2 users 68%Aominé #70

如果您只想在单击按钮时显示一个窗口,并在单击外部(或再次单击按钮)时取消显示此窗口,则以下方法很好:

document.body.onclick = function() { undisp_menu(); };
var menu_on = 0;

function menu_trigger(event) {

    if (menu_on == 0)
    {
        // Otherwise you will call the undisp on body when
        // click on the button
        event.stopPropagation();

        disp_menu();
    }
    else {
        undisp_menu();
    }
}


function disp_menu() {

    menu_on = 1;
    var e = document.getElementsByClassName("menu")[0];
    e.className = "menu on";
}

function undisp_menu() {

    menu_on = 0;
    var e = document.getElementsByClassName("menu")[0];
    e.className = "menu";
}

不要忘记这个按钮:

<div class="button" onclick="menu_trigger(event)">

<div class="menu">

还有 CSS:

.menu {
    display: none;
}

.on {
    display: inline-block;
}
3赞 2 revs, 2 users 84%Muhammet Can TONBUL #71

如果您使用的是“弹出窗口”之类的工具,则可以使用“onFocusOut”事件。

window.onload = function() {
    document.getElementById("inside-div").focus();
}
function loseFocus() {
    alert("Clicked outside");
}
#container {
    background-color: lightblue;
    width: 200px;
    height: 200px;
}

#inside-div {
    background-color: lightgray;
    width: 100px;
    height: 100px;
}
<div id="container">
    <input type="text" id="inside-div" onfocusout="loseFocus()">
</div>

评论

0赞 Peter Mortensen 3/28/2023
这是从哪里复制的?缺少缩进通常是直接从博客文章(或类似文章)中复制的标志。
0赞 Muhammet Can TONBUL 3/28/2023
@PeterMortensen 6年后你问的问题
1赞 chea sotheara #72
$('#propertyType').on("click",function(e){
          self.propertyTypeDialog = !self.propertyTypeDialog;
          b = true;
          e.stopPropagation();
          console.log("input clicked");
      });

      $(document).on('click','body:not(#propertyType)',function (e) {
          e.stopPropagation();
          if(b == true)  {
              if ($(e.target).closest("#configuration").length == 0) {
                  b = false;
                  self.propertyTypeDialog = false;
                  console.log("outside clicked");
              }
          }
        // console.log($(e.target).closest("#configuration").length);
      });
6赞 3 revs, 2 users 71%Rinto George #73

我使用了以下通过jQuery完成的脚本。

jQuery(document).click(function(e) {
    var target = e.target; // Target div recorded
    if (!jQuery(target).is('#tobehide') ) {
        jQuery(this).fadeOut(); // If the click element is not the above id, it will hide
    }
})

下面找到 HTML 代码:

<div class="main-container">
    <div>Hello, I am the title</div>
    <div class="tobehide">I will hide when you click outside of me</div>
</div>

您可以在此处阅读教程。

25赞 3 revs, 3 users 89%Jovanni G #74

还有焦点事件:

var button = document.getElementById('button');
button.addEventListener('click', function(e){
  e.target.style.backgroundColor = 'green';
});
button.addEventListener('focusout', function(e){
  e.target.style.backgroundColor = '';
});
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <button id="button">Click</button>
</body>
</html>

评论

0赞 Merchako 2/16/2023
这个例子在Safari中不起作用,我觉得我应该从中学到一些东西。
4赞 2 revs, 2 users 78%DaniG2k #75

我只是想让皮斯托斯的答案更加明显,因为它隐藏在评论中。

这个解决方案对我来说非常有效。纯 JavaScript:

var elementToToggle = $('.some-element');
$(document).click( function(event) {
  if( $(event.target).closest(elementToToggle).length === 0 ) {
    elementToToggle.hide();
  }
});

CoffeeScript 中:

elementToToggle = $('.some-element')
$(document).click (event) ->
  if $(event.target).closest(elementToToggle).length == 0
    elementToToggle.hide()
4赞 Yair Cohen #76

假设您要检测用户单击外部或内部的 div 有一个 id,例如:“my-special-widget”。

收听正文点击事件:

document.body.addEventListener('click', (e) => {
    if (isInsideMySpecialWidget(e.target, "my-special-widget")) {
        console.log("user clicked INSIDE the widget");
    }
    console.log("user clicked OUTSIDE the widget");
});

function isInsideMySpecialWidget(elem, mySpecialWidgetId){
    while (elem.parentElement) {
        if (elem.id === mySpecialWidgetId) {
            return true;
        }
        elem = elem.parentElement;
    }
    return false;
}

在这种情况下,您不会中断对页面中某些元素的正常点击流程,因为您没有使用“stopPropagation”方法。

-1赞 2 revsRivenfall #77

首先,您必须使用 mouseenter 和 mouseleave 事件跟踪鼠标是在 element1 内部还是外部。 然后,您可以创建一个覆盖整个屏幕的 element2 来检测任何点击,并根据您是在 element1 内部还是外部做出相应的反应。

出于显而易见的原因,我强烈建议同时处理初始化和清理,并且 element2 尽可能临时化。

在下面的示例中,叠加层是位于某处的元素,可以通过单击内部来选择该元素,也可以通过单击外部来取消选择。 _init 和 _release 方法作为自动初始化/清理过程的一部分进行调用。 该类继承自 ClickOverlay,它有一个 inner 和 outerElement,不用担心。我使用了outerElement.parentNode.appendChild来避免冲突。

import ClickOverlay from './ClickOverlay.js'

/* CSS */
// .unselect-helper {
//  position: fixed; left: -100vw; top: -100vh;
//  width: 200vw; height: 200vh;
// }
// .selected {outline: 1px solid black}

export default class ResizeOverlay extends ClickOverlay {
    _init(_opts) {
        this.enterListener = () => this.onEnter()
        this.innerElement.addEventListener('mouseenter', this.enterListener)
        this.leaveListener = () => this.onLeave()
        this.innerElement.addEventListener('mouseleave', this.leaveListener)
        this.selectListener = () => {
            if (this.unselectHelper)
                return
            this.unselectHelper = document.createElement('div')
            this.unselectHelper.classList.add('unselect-helper')
            this.unselectListener = () => {
                if (this.mouseInside)
                    return
                this.clearUnselectHelper()
                this.onUnselect()
            }
            this.unselectHelper.addEventListener('pointerdown'
                , this.unselectListener)
            this.outerElement.parentNode.appendChild(this.unselectHelper)
            this.onSelect()
        }
        this.innerElement.addEventListener('pointerup', this.selectListener)
    }

    _release() {
        this.innerElement.removeEventListener('mouseenter', this.enterListener)
        this.innerElement.removeEventListener('mouseleave', this.leaveListener)
        this.innerElement.removeEventListener('pointerup', this.selectListener)
        this.clearUnselectHelper()
    }

    clearUnselectHelper() {
        if (!this.unselectHelper)
            return
        this.unselectHelper.removeEventListener('pointerdown'
            , this.unselectListener)
        this.unselectHelper.remove()
        delete this.unselectListener
        delete this.unselectHelper
    }

    onEnter() {
        this.mouseInside = true
    }

    onLeave() {
        delete this.mouseInside
    }

    onSelect() {
        this.innerElement.classList.add('selected')
    }

    onUnselect() {
        this.innerElement.classList.remove('selected')
    }
}

评论

3赞 Alejandro Vales 7/25/2019
对于已经给出的答案来说,这太代码了......你为什么要做这个?浪费了很多事件侦听器,只是为了添加 2 个 onclick,一个在元素上,另一个在正文上关闭您想要关闭的元素
1赞 Marcelo Ribeiro #78

const button = document.querySelector('button')
const box = document.querySelector('.box');

const toggle = event => {
  event.stopPropagation();
  
  if (!event.target.closest('.box')) {
    console.log('Click outside');

    box.classList.toggle('active');

    box.classList.contains('active')
      ? document.addEventListener('click', toggle)
      : document.removeEventListener('click', toggle);
  } else {
    console.log('Click inside');
  }
}

button.addEventListener('click', toggle);
.box {
  position: absolute;
  display: none;
  margin-top: 8px;
  padding: 20px;
  background: lightgray;
}

.box.active {
  display: block;
}
<button>Toggle box</button>

<div class="box">
  <form action="">
    <input type="text">
    <button type="button">Search</button>
  </form>
</div>

评论

1赞 Harry Moreno 2/20/2020
对于那些想知道这是如何工作的,回调依赖于浏览器的默认事件传播。如果点击在“框”内,请不要隐藏框。否则,切换类。请参阅,如果用户在框外或按钮上单击,我们需要输入条件。如果我们在文档根目录上切换为 active ON 注册相同的回调,则 ELSE 会从根中删除回调。toggleactive
-3赞 2 revs, 2 users 67%JBarros #79

最简单的方法:mouseleave(function())

更多的信息: https://www.w3schools.com/jquery/jquery_events.asp

评论

1赞 5/19/2020
欢迎提供解决方案的链接,但请确保您的答案在没有它的情况下是有用的:在链接周围添加上下文,以便您的其他用户知道它是什么以及为什么它在那里,然后引用您链接到的页面中最相关的部分,以防目标页面不可用。只不过是链接的答案可能会被删除。
2赞 Paul Roub 5/20/2020
不过,@Daniil这不是一个仅链接的答案。如果删除链接,第一句话仍将构成答案。
2赞 James 7/6/2020
我同意这不是一个仅链接的答案,但它不是这个问题的答案,因为这个答案是当鼠标离开元素时,而不是像;)
22赞 3 revs, 3 users 51%Jesse Reza Khorasanee #80

2020 解决方案使用原生 JS API 最接近的方法。

document.addEventListener('click', ({ target }) => {
  if (!target.closest('#menupop')) {
    document.querySelector('#menupop').style.display = 'none'
  }
})
#menupop {
    width: 300px;
    height: 300px;
    background-color: red;
}
<div id="menupop">
clicking outside will close this
</div>

评论

0赞 Jesse Reza Khorasanee 6/28/2022
对不起,这是如何工作的?我不确定这里指的是什么“.el1”等。
2赞 Mu-Tsun Tsai #81

还在寻找检测外部咔嗒声的完美解决方案吗?不要再看了!介绍Clickout-Event,一个为clickout和其他类似事件提供通用支持的包,它适用于所有场景:纯HTML属性,vanilla JavaScript,jQuery,Vue.js指令,应有尽有。只要前端框架在内部用于处理事件,Clickout-Event 就适用于它。只需在页面中的任何位置添加脚本标签,它就会像魔术一样工作。onclickout.addEventListener('clickout').on('clickout')v-on:clickoutaddEventListener

HTML 属性

<div onclickout="console.log('clickout detected')">...</div>

香草 JavaScript

document.getElementById('myId').addEventListener('clickout', myListener);

jQuery的

$('#myId').on('clickout', myListener);

Vue.js

<div v-on:clickout="open=false">...</div>

<div (clickout)="close()">...</div>
3赞 online Thomas #82

所有这些答案都解决了这个问题,但我想贡献一个 moders es6 解决方案,它完全可以满足需求。我只是希望让某人对这个可运行的演示感到满意。

window.clickOutSide = (element, clickOutside, clickInside) => {
  document.addEventListener('click', (event) => {
    if (!element.contains(event.target)) {
      if (typeof clickInside === 'function') {
        clickOutside();
      }
    } else {
      if (typeof clickInside === 'function') {
        clickInside();
      }
    }
  });
};

window.clickOutSide(document.querySelector('.block'), () => alert('clicked outside'), () => alert('clicked inside'));
.block {
  width: 400px;
  height: 400px;
  background-color: red;
}
<div class="block"></div>

-1赞 2 revs, 2 users 83%aksl #83

这对我来说很好用。我不是专家。

$(document).click(function(event) {
  var $target = $(event.target);
  if(!$target.closest('#hamburger, a').length &&
  $('#hamburger, a').is(":visible")) {
    $('nav').slideToggle();
  }
});
144赞 2 revs, 2 users 90%Cezar Augusto #84

现在是 2020 年,您可以使用event.composedPath()

来自:Event.composedPath()

Event 接口的 composedPath() 方法返回事件的路径,该路径是将调用侦听器的对象数组。

const target = document.querySelector('#myTarget')

document.addEventListener('click', (event) => {
  const withinBoundaries = event.composedPath().includes(target)

  if (withinBoundaries) {
    target.innerText = 'Click happened inside element'
  } else {
    target.innerText = 'Click happened **OUTSIDE** element'
  }
})
/* Just to make it good looking. You don't need this */
#myTarget {
  margin: 50px auto;
  width: 500px;
  height: 500px;
  background: gray;
  border: 10px solid black;
}
<div id="myTarget">
  Click me (or not!)
</div>

评论

0赞 André Mendonça 6/16/2023
这么多答案,但这仍然是迄今为止 2023 年最好的答案。ty(是的,它没有涵盖所有场景,但它是直截了当和现代的。
0赞 Saif Obeidat 8/25/2023
最佳解决方案,比...if (el === event.target || el.contains(event.target))
0赞 6 revs, 2 users 55%ale #85

我已经阅读了 2021 年的所有内容,但如果没有错的话,没有人建议像这样简单的事情来解开绑定和删除事件。使用前面的两个答案和更小的技巧,所以我把所有答案都放在一个(它也可以作为参数添加到函数中以传递选择器,以获得更多弹出窗口)。

也许有人知道这个笑话是有用的。也可以这样完成:

<div id="container" style="display:none"><h1>my menu is nice, but it disappears if I click outside it</h1></div>

<script>
  function printPopup() {
    $("#container").css({"display": "block"});
    var remListener = $(document).mouseup(function (e) {
      if ($(e.target).closest("#container").length === 0 &&
          (e.target != $('html').get(0)))
      {
        //alert('closest call');
        $("#container").css({"display": "none"});
        remListener.unbind('mouseup'); // Isn't it?
      }
    });
  }

  printPopup();

</script>
17赞 2 revs, 2 users 70%tim-mccurrach #86

用于辅助功能focusout

这里有一个答案(非常正确)说,关注事件是一个可访问性问题,因为我们希望迎合键盘用户。该事件是此处使用的正确方法,但它可以比在另一个答案中更简单地完成(在纯 JavaScript 中也是如此):clickfocusout

一种更简单的方法:

使用的“问题”是,如果对话框/模态/菜单中的元素失去焦点,而“内部”元素仍然会被触发。我们可以通过查看来检查情况是否如此(这告诉我们哪些元素将获得焦点)。focusoutevent.relatedTarget

dialog = document.getElementById("dialogElement")

dialog.addEventListener("focusout", function (event) {
    if (
        // We are still inside the dialog so don't close
        dialog.contains(event.relatedTarget) ||
        // We have switched to another tab so probably don't want to close
        !document.hasFocus()
    ) {
        return;
    }
    dialog.close();  // Or whatever logic you want to use to close
});

上面有一个小问题,那就是可能是.如果用户在对话框外部单击,这很好,但如果用户在对话框内单击并且对话框恰好不可聚焦,这将是一个问题。要解决此问题,您必须确保设置,以便您的对话框可聚焦。relatedTargetnulltabIndex=0

评论

0赞 Vladimir Jovanović 5/12/2021
这是迄今为止最好的解决方案,因为它考虑了可访问性。
0赞 Juan Lanus 3/11/2022
...或 'tabIndex=“-1”,因此它不会插入到 Tab 键序列中
0赞 catwith 6/29/2022
注意:您不能将其用于不可聚焦的元素
0赞 Tuan Chau 5/7/2023
在我自己想出解决方案后,我看到了这个答案。将事件绑定到 document/window/body 的其他解决方案存在一个问题:如果元素使用 stop propagate 捕获事件,则不会执行侦听器。
2赞 Normajean #87

这是我找到的这个问题的最简单答案:

window.addEventListener('click', close_window = function () {
  if(event.target !== windowEl){
    windowEl.style.display = "none";
    window.removeEventListener('click', close_window, false);
  }
});

您将看到我将函数命名为“close_window”,以便在窗口关闭时删除事件侦听器。

2赞 Sommelier #88

一种用纯 JavaScript 编写的方法

let menu = document.getElementById("menu");

document.addEventListener("click", function(){
    // Hide the menus
    menu.style.display = "none";
}, false);

document.getElementById("menuscontainer").addEventListener("click", function(e){
    // Show the menus
    menu.style.display = "block";
    e.stopPropagation();
}, false);
0赞 jameshfisher #89

你不需要(太多)JavaScript,只需要选择器::focus-within

  • 用于显示侧边栏。.sidebar:focus-within
  • 设置在侧边栏和正文元素上,使它们可聚焦。tabindex=-1
  • 使用 和 设置侧边栏可见性。sidebarEl.focus()document.body.focus()

const menuButton = document.querySelector('.menu-button');
const sidebar = document.querySelector('.sidebar');

menuButton.onmousedown = ev => {
  ev.preventDefault();
  (sidebar.contains(document.activeElement) ?
    document.body : sidebar).focus();
};
* { box-sizing: border-box; }

.sidebar {
  position: fixed;
  width: 15em;
  left: -15em;
  top: 0;
  bottom: 0;
  transition: left 0.3s ease-in-out;
  background-color: #eef;
  padding: 3em 1em;
}

.sidebar:focus-within {
  left: 0;
}

.sidebar:focus {
  outline: 0;
}

.menu-button {
  position: fixed;
  top: 0;
  left: 0;
  padding: 1em;
  background-color: #eef;
  border: 0;
}

body {
  max-width: 30em;
  margin: 3em;
}
<body tabindex='-1'>
  <nav class='sidebar' tabindex='-1'>
    Sidebar content
    <input type="text"/>
  </nav>
  <button class="menu-button">☰</button>
  Body content goes here, Lorem ipsum sit amet, etc
</body>

0赞 2 revs, 2 users 90%Felix Furtmayr #90

对于那些想要将简短的解决方案集成到他们的 JavaScript 代码中的人来说,一个没有 jQuery 的小库:

用法:

// Demo code
var htmlElem = document.getElementById('my-element')
function doSomething(){ console.log('outside click') }

// Use the library
var removeListener = new elemOutsideClickListener(htmlElem, doSomething);

// Deregister on your wished event
$scope.$on('$destroy', removeListener);

这是库:


function elemOutsideClickListener (element, outsideClickFunc, insideClickFunc) {
   function onClickOutside (e) {
      var targetEl = e.target; // clicked element
      do {
         // click inside
         if (targetEl === element) {
            if (insideClickFunc) insideClickFunc();
            return;

         // Go up the DOM
         } else {
            targetEl = targetEl.parentNode;
         }
      } while (targetEl);

      // click outside
      if (!targetEl && outsideClickFunc) outsideClickFunc();
   }

   window.addEventListener('click', onClickOutside);

   return function () {
      window.removeEventListener('click', onClickOutside);
   };
}

我从这里获取代码并将其放入一个函数中: 如何检测元素外部的点击

-1赞 3 revs, 2 users 56%Edgar #91

这是容器或整个文档中的解决方案。如果点击目标不在元素中(类为“yourClass”),则元素将被隐藏。

$('yourContainer').on('click', function(e) {
  if (!$(e.target).hasClass('yourClass')) {
    $('.yourClass').hide();
  }
});

评论

2赞 Tyler2P 1/12/2023
通过添加有关代码的作用以及它如何帮助 OP 的更多信息,可以改进您的答案。
0赞 Peter Mortensen 1/31/2023
这不是 Stack Overflow 的用途。解释对于一个好的答案至关重要。”