4

我创建了一个 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'} 的意义,但无论如何它都不起作用。

我有一个有效的黑客,但无法想象我必须使用它。
封装的全部意义在于事物可以是自包含的,但是如果 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等)的注意事项 - 当我需要关闭模式时,这些答案将不起作用。

4

1 回答 1

2

看起来您在元素引用和this范围(在<script>)中都有错误

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


<script>在一个<template>(这个范围)内

<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:closedmode:open

<script> 范围将是window(因为没有分配范围)

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

要获得内部的组件范围,<script>您必须具有创造力..
并使用您img onload=的“hack”

使用onloadon<style>元素使其不再是黑客(恕我直言)

<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>

一个主要问题:只会对第一个使用的<hello-world>元素执行!

我没有写这个,
(正在工作)JSFiddle游乐场(也显示引用组件方法)在:
https ://jsfiddle.net/CustomElementsExamples/zpamx728/

更新#1

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

  • onloadon a<STYLE>元素只会触发第一个元素我将JSFiddle
    更改为使用您的第一个 hack<img>

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

!!!更新 #2 !!!

哎呀!用它玩了一些。

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

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

这也是 FireFox 抱怨重新声明let变量的原因。

于 2020-02-06T17:18:08.020 回答