如何针对<content>下的Shadow DOM事件进行定位?

8
我正在尝试理解从Light DOM发起的事件在通过<content>元素接收到Shadow DOM后的情况。我正在阅读Shadow DOM W3C Draft,但我并不完全理解,但它似乎表明事件需要从EventListener附加的角度进行“重新定向”。

在事件路径跨越多个节点树的情况下,事件对事件目标的信息进行调整以保持封装性。事件重定向是计算派发事件节点祖先的相对目标的过程。相对目标是在给定祖先处最准确地表示派发事件目标的节点,同时保持封装性。

以及

事件派发时:

  • 事件目标和currentTarget属性必须返回在调用事件侦听器的节点上的相对目标

因此,这里有一个简单的Polymer自定义元素,它只是将其子项放入容器中,并将click事件侦听器添加到容器中(位于shadow DOM中)。在这种情况下,子项是一个按钮。

<!DOCTYPE html>
<html>
  <head>
    <script src="bower_components/platform/platform.js"></script>
    <link rel="import" href="bower_components/polymer/polymer.html">
  </head>
  <body unresolved>
    <polymer-element name="foo-bar">
      <template>
        <div id="internal-container" style="background-color:red; width:100%;">
          <content></content>
        </div>
      </template>
      <script>
       Polymer("foo-bar", {
         clickHandler: function(event) {
           console.log(event);
           var element = event.target;
           while (element) {
             console.log(element.tagName, element.id);
             element = element.parentElement;
           }
         },

         ready: function() {
           this.shadowRoot.querySelector('#internal-container').addEventListener('click', this.clickHandler);
         }
       });
      </script>
    </polymer-element>

    <foo-bar id="custom-element">
      <button>Click me</button>
    </foo-bar>
  </body>
</html>

当我在谷歌浏览器38.0.2075.0的金丝雀版本上运行此代码时,当我点击按钮时,会出现以下结果:
MouseEvent {dataTransfer: null, toElement: button, fromElement: null, y: 19, x: 53}altKey: falsebubbles: truebutton: 0cancelBubble: falsecancelable: truecharCode: 0clientX: 53clientY: 19clipboardData: undefinedctrlKey: falsecurrentTarget: nulldataTransfer: nulldefaultPrevented: falsedetail: 1eventPhase: 0fromElement: nullkeyCode: 0layerX: 53layerY: 19metaKey: falsemovementX: 0movementY: 0offsetX: 45offsetY: 10pageX: 53pageY: 19path: NodeList[0]relatedTarget: nullreturnValue: truescreenX: 472screenY: 113shiftKey: falsesrcElement: buttontarget: buttontimeStamp: 1404078533176toElement: buttontype: "click"view: WindowwebkitMovementX: 0webkitMovementY: 0which: 1x: 53y: 19__proto__: MouseEvent test.html:17
BUTTON  test.html:20
FOO-BAR custom-element test.html:20
BODY  test.html:20
HTML  test.html:20

当我点击容器时,我会得到:

MouseEvent {dataTransfer: null, toElement: div#internal-container, fromElement: null, y: 15, x: 82} test.html:17
DIV internal-container test.html:20

根据元素所在的DOM,我会在光影DOM中获取事件目标。我原本期望在两种情况下都从影子DOM中获取目标,因为事件监听器是附加在那里的。我的问题是:

  1. 这是预期的工作方式吗?
  2. 如果是,有没有其他方法可以将从光DOM冒泡的事件重新定位到影子DOM?

如果有人想问:“你想做什么?”我并没有特别想做什么,只是想理解这种行为。

1个回答

14

有阴影DOM的事件比较棘手。我会在下面记录一些想法。

  1. 这是它应该工作的方式吗?

没错。如果你在Chrome中进行测试,你会得到原生的阴影DOM。


我在HTML5Rocks - Shadow DOM 301文章中写了一个关于事件重新定向的部分。基本上,重新定向意味着在阴影DOM中发起的事件看起来像来自元素本身。

在您的示例中,您正在记录阴影DOM内部的事件,因此它仍然在那里可见。如果您还在元素外添加了“click”侦听器,则目标将看起来像来自该元素:

<script>
  var el = document.querySelector('#custom-element');
  el.addEventListener('click', function(e) {
    console.log(e.target.tagName); // logs FOO-Bar
  });
</script>

http://jsbin.com/womususe/1/edit

'click' 事件会冒泡。这就是为什么在您的顶部示例中看到了 BUTTON。你为什么能看到它?因为按钮不是您元素的影子 DOM 的一部分。它在光 DOM 中且是元素的目标。重要的是要记住,光 DOM 节点仍然在主文档中逻辑上存在。它们没有移动到影子 DOM 中,而只是呈现在 <content> 插入点处。


顺便说一句,在您的示例中有几个 Polymer 化的修复:

  1. this.shadowRoot.querySelector('#internalcontainer') -> this.$.internalcontainerthis.$.ID 是 Polymer 的“自动节点查找”功能。
  2. 您完全不需要使用 addEventListener()。取而代之,请使用 <div id="internalcontainer" on-click="{{clickHandler}}">。这是一种声明性的事件处理程序。

感谢您的回复(以及您在世界上发布的所有其他信息)!我知道按钮不是阴影DOM的一部分;这就是为什么我对在阴影DOM侦听器上看到它感到惊讶的原因。规范说:“事件目标和currentTarget属性必须返回在调用事件侦听器的节点上相对于目标的相对目标。”,所以我期望通过<content>元素传递的事件被重新定位。只是为了再次明确,您是否在说符合规范的阴影DOM侦听器可以从其主机的DOM以及其自己的DOM接收事件目标? - rhashimoto
1
是的,这是正常的。事件重新定向是朝着另一个方向进行的:防止外部DOM看到ShadowDOM内部的节点。 - Scott Miles
@ScottMiles 假设影子 DOM 中的一个级别为-10的元素触发了“response”事件。通过重新定位,它将在所有10个级别(主机)上进行传播。同时,级别为-5的元素也会触发名为“response”的事件,并且对于这个事件也会进行重新定位。如果在级别-4上,您想要为级别-5上的“response”事件附加处理程序(不知道在级别-10上触发的事件),它也将不情愿地处理来自-10的事件。在中型或大型应用程序中,您不可能知道所有来自深层的“水下”的事件。您对此有何看法?谢谢。 - adriann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接