首页 > JavaScript > 如何检测元素外部的点击?

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

上一篇 下一篇

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

jQuery可以这样的事情吗?

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

分割线

网友回答:

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

如果不是,则单击的元素在 外部,您可以安全地隐藏它。#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的人。这是上面的普通代码(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()
        }
    }

分割线

网友回答:

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

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

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

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

分割线

网友回答:

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

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

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

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

提示:是“咔嚓”字!

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

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

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

用户完成对话框后如何关闭对话框?

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

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

focusout事件

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

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

jQuery会做得很好。如果你不能使用 jQuery,那么你可以在捕获阶段使用:focusoutblur

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.key

如果您认为通过处理焦点状态已完成,则可以执行更多操作来简化用户体验。

这通常是一个“很高兴拥有”的功能,但通常当您有任何类型的模式或弹出窗口时,键会将其关闭。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 角色和属性的讨论,但我强烈建议实现者参考规范以获取有关他们应该使用的角色和任何其他适当属性的详细信息。

模板简介:该模板名称为【如何检测元素外部的点击?】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【JavaScript】栏目查找您需要的精美模板。

相关搜索
  • 下载密码 lanrenmb
  • 下载次数 185次
  • 使用软件 Sublime/Dreamweaver/HBuilder
  • 文件格式 编程语言
  • 文件大小 暂无信息
  • 上传时间 03-04
  • 作者 网友投稿
  • 肖像权 人物画像及字体仅供参考
栏目分类 更多 >
热门推荐 更多 >
微信模板 微信图片 企业网站 微信素材 微信公众平台 响应式 自适应 微信文章 html5 单页式简历模板
您可能会喜欢的其他模板