【问题标题】:Determine if user clicked outside shadow dom确定用户是否在 shadow dom 外部单击
【发布时间】:2016-09-19 02:13:24
【问题描述】:

我正在尝试实现一个下拉菜单,您可以在外部单击以关闭它。下拉列表是自定义日期输入的一部分,并封装在输入的影子 DOM 中。

我想写这样的东西:

window.addEventListener('mousedown', function (evt) {
  if (!componentNode.contains(evt.target)) {
    closeDropdown();
  }
});

然而,事件被重新定位,所以evt.target 总是在元素之外。事件在到达窗口之前将跨越多个阴影边界,因此似乎无法真正知道用户是否在我的组件内单击。

注意:我没有在任何地方使用聚合物——我需要一个适用于通用影子 DOM 的答案,而不是聚合物特定的 hack。

【问题讨论】:

  • ...应该是window,因为我正在将事件监听器添加到窗口中。
  • 我们可以提供一个小提琴/例子吗?
  • “事件在到达窗口之前会跨越多个阴影边界,因此似乎无法真正知道用户是否在我的组件内部单击。” i> 如果单击,event.target 将是 host 元素

标签: javascript shadow-dom


【解决方案1】:

您可以尝试使用event 对象的path 属性。 还没有找到它的实际参考,MDN 还没有它的页面。 HTML5Rocks 在 shadow dom 教程中有一小部分关于它。因此,我不知道跨浏览器的兼容性。

找到关于事件路径的W3 Spec,不确定这是否完全适用于Event.path 属性,但它是我能找到的最接近的参考。

如果有人知道对Event.path 的实际规范引用(如果链接的规范页面还没有),请随时编辑。

它保存事件经过的路径。它将包含位于 shadow dom 中的元素。列表中的第一个元素 (path[0]) 应该是实际点击的元素。请注意,您需要从 shadow dom 引用中调用 contains,例如 shadowRoot.contains(e.path[0]) 或 shadow dom 中的某些子元素。

演示:点击菜单展开,点击菜单项以外的任何地方都会关闭菜单。

var host = document.querySelector('#host');
var root = host.createShadowRoot();
d = document.createElement("div");
d.id = "shadowdiv";

d.innerHTML = `
  <div id="menu">
    <div class="menu-item menu-toggle">Menu</div>
    <div class="menu-item">Item 1</div>
    <div class="menu-item">Item 2</div>
    <div class="menu-item">Item 3</div>
  </div>
  <div id="other">Other shadow element</div>
`;
var menuToggle = d.querySelector(".menu-toggle");
var menu = d.querySelector("#menu");
menuToggle.addEventListener("click",function(e){
  menu.classList.toggle("active");
});
root.appendChild(d)

//Use document instead of window
document.addEventListener("click",function(e){
  if(!menu.contains(e.path[0])){
    menu.classList.remove("active");
  }
});
#host::shadow #menu{
  height:24px;
  width:150px;
  transition:height 1s;
  overflow:hidden;
  background:black;
  color:white;
}
#host::shadow #menu.active {
  height:300px;
}
#host::shadow #menu .menu-item {
  height:24px;
  text-align:center;
  line-height:24px;
}

#host::shadow #other {
  position:absolute;
  right:100px;
  top:0px;
  background:yellow;
  width:100px;
  height:32px;
  font-size:12px;
  padding:4px;
}
&lt;div id="host"&gt;&lt;/div&gt;

【讨论】:

  • 注意,因为&lt;div&gt; widthwidthwindow,如果点击发生在&lt;button&gt; 的右侧,true 记录在console
  • @guest271314,这实际上是预期的,因为它是影子根内部的div,所以shadowRoot.contains() 会为此返回true。添加了实际元素的日志以显示它是内部的 div
  • @guest271314,修改示例以显示更多我认为 OP 的目标。在 sn-p 中单击除菜单项以外的任何内容都会关闭菜单。如果他们使用event.target,因为它被重定向到主机,您将无法区分菜单项和shadow dom中的任何其他元素。
  • @PatrickEvans 现在,您可以使用Event.composedPath。请参阅 DOM 标准中的 Event interface“返回事件路径的项目对象(将在其上调用侦听器的对象),但影子树中影子根的模式为“关闭”的任何节点除外无法从事件的 currentTarget 访问。”
【解决方案2】:

shadowRootevent.target 将是host 元素。要关闭shadowDOM 中的&lt;select&gt; 元素,如果event.target 不是host 元素,您可以使用if (evt.target !== hostElement),然后在hostElement 上调用.blur()

var input = document.querySelector("input");
var shadow = input.createShadowRoot();
var template = document.querySelector("template");
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);

window.addEventListener("mousedown", function (evt) {
  if (evt.target !== input) {
    input.blur();
  }
});
<input type="date" />
<template>
  <select>
    <option value="1999">1999</option>
    <option value="2000">2000</option>
  </select>
</template>

【讨论】:

  • 如果只有一层shadow DOM,那么事件将被重定向到宿主元素。但是如果宿主元素在另一个元素的影子 DOM 中,那么事件将在到达文档/窗口上的事件处理程序之前被重定向 两次。抱歉,如果我对此不清楚。
【解决方案3】:

另一种选择是检查事件光标对目标元素的偏移量:

listener(event) {
    const { top, right, bottom, left } = targetElement.getBoundingClientRect();
    const { pageX, pageY } = event;
    const isInside = pageX >= left && pageX <= right && pageY >= top && pageY <= bottom;
}

【讨论】:

    【解决方案4】:

    由于声誉问题无法发表评论,但想分享使用composedPath 的外观。见Determine if user clicked outside shadow dom

    document.addEventListener("click",function(e){
      if(!e.composedPath().includes(menu)){
        menu.classList.remove("active");
      }
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-18
      • 1970-01-01
      • 2019-09-06
      • 1970-01-01
      • 2017-09-02
      • 2018-10-14
      • 1970-01-01
      相关资源
      最近更新 更多