简体   繁体   English

从 Shadow DOM 中访问元素

[英]Access Element from within Shadow DOM

I have created a web component, and would like to access the elements from within the component.我创建了一个 Web 组件,并希望组件访问元素。
Am using .attachMode({mode:'closed'}), so the parent has no access.正在使用 .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>

Some attempts:一些尝试:

  1. Within the document fragment - this, self, window, and document all refer to the parent window.在文档片段中——this、self、window 和 document 都指向父窗口。 And none can access the shadow root.并且没有人可以访问影子根。
  2. Tried to store the shadowroot in a global variable and access it from inside the fragment or connectedCallback.尝试将 shadowroot 存储在全局变量中并从片段或 connectedCallback 内部访问它。
    Even if that worked, it would defeat the point of using {mode:'closed'}, but anyways it did not work.即使那行得通,它也会失去使用 {mode:'closed'} 的意义,但无论如何它不起作用。

I have a hack that works, but cannot imagine that I have to use it.我有一个有效的 hack,但无法想象我必须使用它。
The whole point of encapsulation is that things can be self contained, but what good does that do us if the JS cannot act on the other items in its container?封装的全部意义在于事物可以是自包含的,但是如果 JS 不能对其容器中的其他项进行操作,这对我们有什么好处?

If this is the solution, would love a tip to explain the logic of the way components where implemented.如果这是解决方案,希望有一个技巧来解释实现组件的方式的逻辑。
Here's the hack, though: include an image that runs the JS onload.不过,这里有一个技巧:包含一个运行 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>

Note regarding similar questions ( 25048359 , 16633057 , 55101967 , etc.) - those answers will not work when mode is closed, which I need.关于类似问题的注意事项( 250483591663305755101967等) - 当模式关闭时,这些答案将不起作用,这是我需要的。

Looks like you have errors in both Element references and this Scope (in <script> )看起来您在 Element 引用和this Scope 中都有错误(在<script>

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

appendChild is your Nemesis here. appendChild是你的复仇女神。

It returns the inserted element... not a shadow-root but a #document-fragment (cloned template)它返回插入的元素......不是影子根而是#document-fragment (克隆模板)

fixed with:固定:

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

Then:然后:

  • mode:closed will not assign this.shadowRoot .. mode:closed不会分配this.shadowRoot ..
    which you can not re-use because it still is a read-only property不能重复使用,因为它仍然是只读属性

fixed with:固定:

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

You can now do:你现在可以这样做:

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

I do not understand why you consider this not ideal我不明白你为什么认为这不理想
this.Root is accessible from all/any methods inside the Component this.Root可以从组件的所有/任何方法访问

A good resource for all DOM methods is: https://javascript.info/modifying-document所有 DOM 方法的一个很好的资源是: https : //javascript.info/modifying-document


<script> inside a <template> (this scope) <script> <template> <script>内的<script> (此范围)

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

You injected the template into a Component您将模板注入到组件中
document can not access elements inside any Component shadowDOM document无法访问任何组件 shadowDOM内的元素
doesn't matter if the shadowDOM is mode:closed or mode:open shadowDOM 是mode:closed还是mode:open无关紧要

The <script> scope will be window (since it wasn't assigned a scope) <script>范围将是window (因为它没有分配范围)

(I do not think you can set the this scope for a SCRIPT) (我认为您不能为 SCRIPT 设置范围)

To get the Component scope inside that <script> you have to be creative ..要在<script>获取 Component 范围,您必须具有创造性..
and use your img onload= 'hack'并使用您的img onload= ' hack'

With the onload on the <style> element makes it a bit less of a hack (IMHO)随着<style>元素上的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>

one major issue: will only execute for the first used <hello-world> element!!一个主要问题:只会对第一个使用的<hello-world>元素执行!!

I didn't write this of the top of my head,我没有在头顶写这个,
(Work in progess) JSFiddle playground (also showing referencing Component methods ) at: (正在进行中)JSFiddle 游乐场(还显示了引用组件方法)位于:
https://jsfiddle.net/CustomElementsExamples/zpamx728/ https://jsfiddle.net/CustomElementsExamples/zpamx728/

Update #1更新 #1

Chromium (Edge/Chrome) and Opera are fine, FireFox v72.0.2 misbehaves: Chromium (Edge/Chrome) 和 Opera 很好,FireFox v72.0.2

  • the onload on a <STYLE> element will only fire for the first element所述onload上的<STYLE>元素将仅针对所述第一元件
    I changed the JSFiddle to use your first hack using an <img> , and it fires for every element我使用<img>更改了 JSFiddle 以使用您的第一个 hack,并且它为每个元素触发

  • templFunc() is loaded/scoped in shadowDOM, so is callable from component methods (see JSFiddle) templFunc()在 shadowDOM 中加载/作用域,因此可以从组件方法调用(参见 JSFiddle)
    but.. only in FireFox is NOT defined/available for the first element但是.. 仅在 FireFox 中未为第一个元素定义/可用
    For now I consider this a FireFox bug... will investigate further... (to boldly go where...)现在我认为这是一个 FireFox 错误......将进一步调查......(大胆地去哪里......)

!!! !!! Update #2 !!!更新 #2 !!!

OOPS!哎呀! Played some more with it.玩了一些。

Turns out all variables and functions from a cloned and imported SCRIPT从克隆和导入的 SCRIPT 中生成所有变量和函数
are hoisted to the global window scope被提升到全局window范围

So above code works, but has a major side-effect...所以上面的代码有效,但有一个主要的副作用......

That is also why FireFox complains about re- declaring let variables这也是 FireFox 抱怨重新声明let变量的原因

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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