简体   繁体   中英

Determine if user clicked outside shadow dom

I'm trying to implement a dropdown which you can click outside to close. The dropdown is part of a custom date input and is encapsulated inside the input's shadow DOM.

I want to write something like:

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

however, the event is retargeted, so evt.target is always the outside the element. There are multiple shadow boundaries that the event will cross before reaching the window, so there seems to be no way of actually knowing if the user clicked inside my component or not.

I'm not using polymer anywhere -- I need an answer which applies to generic shadow DOM, not a polymer specific hack. 我没有在任何地方使用聚合物 - 我需要一个适用于通用阴影DOM的答案,而不是聚合物特定的黑客。

You can try using the path property of the event object. Haven't found a actual reference for it and MDN doesn't yet have a page for it. HTML5Rocks has a small section about it in there shadow dom tutorials though. As such I do not know the compatibility of this across browsers.

Found the W3 Spec about event paths, not sure if this is meant exactly for the Event.path property or not, but it is the closest reference I could find.

If anyone knows an actual spec reference to Event.path (if the linked spec page isn't already it) feel free to edit it in.

It holds the path the event went through. It will contain elements that are in a shadow dom. The first element in the list ( path[0] ) should be the element that was actually clicked on. Note you will need to call contains from the shadow dom reference, eg shadowRoot.contains(e.path[0]) or some sub element within your shadow dom.

Demo: Click menu to expand, clicking anywhere except on the menu items will close menu.

 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; } 
 <div id="host"></div> 

The event.target of the shadowRoot would be the host element. To close a <select> element within shadowDOM if event.target is not host element you can use if (evt.target !== hostElement) , then call .blur() on hostElement

 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> 

Another option is to check the event cursor offsets against the target element:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM