处理 Web 组件时的香草 JavaScript 事件委托

Vanilla JavaScript event delegation when dealing with Web Components

我当前的项目使用 Web 组件(自定义元素和阴影 DOM),它们允许我封装复杂的逻辑和样式,远离 Light DOM。

不幸的是,我现在遇到了一个问题,我需要能够随意切换元素,而无需解除绑定和重新绑定事件处理程序的麻烦。

这听起来像是我的事件委托工作,所以我尝试将事件侦听器添加到 Light DOM 中的 parent 节点,希望事件会从 Shadow [=] 中冒出来35=].

这似乎违背了 Shadow DOM 的封装,并且 event.target 始终是 ShadowRoot 而不是 child。

在这种情况下,我可以做些什么来允许经典事件委托?下面的代码片段显示了这个问题。我希望能够单击内部 DIV 并在单击事件处理程序中处理单击,但 event.target 始终是 custom-el

/* jshint esversion: 6 */

customElements.define('custom-el', class extends HTMLElement {

 constructor() {
  super();

  this._shadowRoot = this.attachShadow({
   mode: 'open'
  });

  const oInnerDiv = document.createElement('div');
  oInnerDiv.classList.add('inner');
    oInnerDiv.style.border = '2px solid blue';
    oInnerDiv.style.padding = '3rem';
  this._shadowRoot.appendChild(oInnerDiv);
 }

});

document.addEventListener('click', oEvent => {
 document.getElementById('result').innerText = oEvent.target.tagName;
}, true);
html {
 box-sizing: border-box;
}

*,
*::before,
*::after {
 box-sizing: inherit;
}

body {
 margin: 0;
 padding: 0;
}

main,
div,
custom-el {
 display: inline-block;
 border: 2px solid black;
 padding: 3rem;
}
<main>
    <custom-el>
</main>
  
<p id="result"></p>

如果阴影DOM模式是open,可以借助Event.composedPath()方法来点击内部元素,这将return交叉节点的数组(先是内部节点)。

document.addEventListener('click', oEvent => {
    result.innerText = oEvent.composedPath()[0].tagName;
}, true);

此方法取代了旧的 Event.path 属性。

customElements.define('custom-el', class extends HTMLElement {
  constructor() {
    super();
    this._shadowRoot = this.attachShadow({ mode: 'open' });
    const oInnerDiv = document.createElement('div');
    oInnerDiv.classList.add('inner');
    oInnerDiv.style.border = '2px solid blue';
    oInnerDiv.style.padding = '1rem';
  this._shadowRoot.appendChild(oInnerDiv);      
  }
});

document.addEventListener('click', oEvent => {
  result.innerText = oEvent.composedPath()[0].tagName;
});
html {
 box-sizing: border-box;
}

*,
*::before,
*::after {
 box-sizing: inherit;
}

body {
 margin: 0;
 padding: 0;
}

main,
div,
custom-el {
 display: inline-block;
 border: 2px solid black;
 padding: 1rem;
}
<main>
    <custom-el></custom-el>
</main>
  
<p id="result"></p>