【问题标题】:Access Element from within Shadow DOM从 Shadow DOM 中访问元素
【发布时间】:2020-02-06 14:53:09
【问题描述】:

我创建了一个 Web 组件,并希望从组件内部访问元素。
我正在使用 .attachMode({mode:'closed'}),所以父级无权访问。

<template id='hello-world-template'>
  <span id='inside'>Unchanged,</span> <span id='outside'>Unchanged</span>
  <script>
    document.querySelector('#inside').innerHTML = 'Changed';
    // Ideal, but does not work - no such element 
  </script>
</template>
<hello-world></hello-world>

<script>
customElements.define('hello-world',
   class extends HTMLElement {
      constructor() {
         super();
         var template = document.getElementById('hello-world-template')
         var clone = template.content.cloneNode(true)
         const shadowRoot = this.attachShadow({mode: 'closed'}).appendChild(clone);
         }
      connectedCallback() {
        this.shadowRoot.querySelector('#outside').innerHTML = 'Changed';
        // Not ideal, and also does not work - this.shadowRoot has no querySelector
        }
      });
</script>

一些尝试:

  1. 在文档片段中 - this、self、window 和 document 都指向父窗口。而且没有人可以访问影子根。
  2. 试图将 shadowroot 存储在全局变量中,并从片段或 connectedCallback 内部访问它。
    即使这样可行,它也会破坏使用 {mode:'closed'} 的意义,但无论如何它不起作用。

我有一个可行的 hack,但无法想象我必须使用它。
封装的全部意义在于事物可以自包含,但如果 JS 不能作用于其容器中的其他项目,这对我们有什么好处?

如果这是解决方案,希望有一个提示来解释组件实现方式的逻辑。
不过,这是 hack:包含一个运行 JS onload 的图像。

<template id='hello-world-template'>
  <span id='inside'>Unchanged,</span> <span id='outside'>Unchanged</span>
  <script>
    function runner(img){
       let doc = img.parentNode;
       doc.querySelector('#inside').innerHTML = 'Changed';
       }
  </script>
  <img src='data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' onload="runner(this)">
</template>
<hello-world></hello-world>

关于类似问题(250483591663305755101967 等)的注意事项 - 当模式关闭时,这些答案将不起作用,这是我需要的。

【问题讨论】:

  • 您可以通过以下方式缩短您的 hack:&lt;img src='' onerror="runner(this)"/&gt;
  • 可爱!或&lt;img src='//:0' onload='runner(this)'&gt;.

标签: javascript web-component shadow-dom documentfragment


【解决方案1】:

看起来您在元素引用和this 范围(&lt;script&gt;)中都有错误

const shadowRoot = this.attachShadow({mode: 'closed'}).appendChild(clone);

appendChild 是你的克星。

它返回插入的元素...不是影子根,而是#document-fragment(克隆模板)

固定为:

 const shadowRoot = this.attachShadow({mode: 'closed'});
 shadowRoot.appendChild(clone);

然后:

  • mode:closed分配this.shadowRoot ..
    您可以重复使用它,因为它仍然是只读属性

固定为:

 this.Root = this.attachShadow({mode: 'closed'});
 this.Root.appendChild(clone);

您现在可以这样做:

  connectedCallback() {
    this.Root.querySelector('#outside').innerHTML = 'Changed';
  }

我不明白您为什么认为这不理想
this.Root 可以通过内部组件的所有/任何方法访问

所有 DOM 方法的好资源是:https://javascript.info/modifying-document


&lt;script&gt;&lt;template&gt; 内(这个范围)

<template id='hello-world-template'>
  <span id='inside'>Unchanged</span>
  <script>
    document.querySelector('#inside').innerHTML = 'Changed';
    // Ideal, but does not work - no such element 
  </script>
</template>

您将模板注入到组件中
document 不能访问元素内部任何组件 shadowDOM
shadowDOM 是 mode:closed 还是 mode:open 无关紧要

&lt;script&gt; 范围 将是 window(因为它没有分配范围)

(我认为您不能为 SCRIPT 设置 this 范围)

要在 &lt;script&gt; 内获得组件范围,您必须具有创造性..
并使用你的img onload='hack'

&lt;style&gt; 元素上使用onload 使其不再是黑客攻击(恕我直言)

<template id='hello-world-template'>
  <span id='inside'>Inside Unchanged,</span>
  <script>
    function templFunc() {
      // this scope is the shadowRoot, not the component!
      this.querySelector('#inside').innerHTML = 'Changed';
    }
  </script>
  <style onload="templFunc.apply(this.getRootNode())">
    #inside{
      color:green;
    }
  </style>
</template>

一个主要问题:只会对第一个使用的&lt;hello-world&gt; 元素执行!!

这不是我脑子里写的,
(正在进行中)JSFiddle 游乐场(还显示了引用组件方法):
https://jsfiddle.net/CustomElementsExamples/zpamx728/

更新 #1

Chromium (Edge/Chrome) 和 Opera 都很好,FireFox v72.0.2 行为不端:

  • &lt;STYLE&gt; 元素上的 onload 只会为 第一个元素触发
    我将 JSFiddle 更改为使用您的第一个 hack,使用 &lt;img&gt;,它会为每个元素触发

  • templFunc() 在 shadowDOM 中加载/作用域,因此可从组件方法调用(请参阅 JSFiddle)
    但.. 仅在 FireFox 中 未定义/不适用于 第一个元素
    目前我认为这是一个 FireFox 错误...将进一步调查...(大胆去哪里...)

!!!更新 #2 !!!

哎呀!用它玩了一些。

原来克隆和导入的 SCRIPT 中的所有变量和函数
都被提升到全局 window 范围

所以上面的代码有效,但有一个主要的副作用......

这也是 FireFox 抱怨 re- 声明 let 变量的原因

【讨论】:

  • 回答的很清楚,谢谢! “this.Root 可以从组件内的所有/任何方法访问” 是什么意思?组件内部的console.log(this.Root) 返回未定义。您的意思是父级可以访问组件内的所有元素吗?
  • 查看JSFiddle this.Rootconstructor 中设置,然后在connectedCallbackAmethod 方法中使用。
  • 我看到了小提琴,但仍然不清楚。 connectedCallback 和 Amethod 都定义在组件之外(在 Window 中)。如果在组件内部定义了 Amethod,它就无法访问 this.Root,对吗?
  • 不理想 - 因为我希望我的所有组件代码都在组件中(类似于组件内部的 CSS)。而且因为如果我创建一个与 this.shadowRoot({mode:'open'}) 具有相同权限的引用 (this.Root),那么我很可能刚刚将 shadowRoot 与 {mode:'open'} 分开。如果这是错误的,那么请说!
  • 不太清楚你在追求什么......this JSFiddle'关闭'足够了吗?没有更多的connectedCallback constructor 加载模板(你称之为组件?我会说类是组件)因此从组件中的模板脚本执行render 函数实例上下文/范围。该模板脚本中的所有内容都是私有的; 可选地您可以添加公共方法(在渲染函数中声明,以便您可以访问您的元素私有)
猜你喜欢
  • 2015-05-08
  • 2021-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-24
  • 1970-01-01
  • 2020-03-16
  • 1970-01-01
相关资源
最近更新 更多