【问题标题】:Shadow DOM innerHTML with slots replaced by assignedNodes()Shadow DOM innerHTML 的插槽被 assignedNodes() 替换
【发布时间】:2022-12-28 18:08:08
【问题描述】:

我正在使用 Shadow DOM 处理 customElement,例如:

<hello-there><b>S</b>amantha</hello-there>

而 innerHTML(在我的例子中由 lit/lit-element 生成)是这样的:

<span>Hello <slot></slot>!</span>

我知道,如果 const ht = document.querySelector('hello-there') 我可以调用 .innerHTML 并获得 &lt;b&gt;S&lt;/b&gt;amantha 并且在 ht 的 shadowRoot 上,我可以调用 .innerHTML 并获得 &lt;span&gt;Hello &lt;slot&gt;&lt;/slot&gt;!&lt;/span&gt;。但...

如果我已经表达(没有 ShadowDOM)HTML &lt;span&gt;Hello &lt;b&gt;S&lt;/b&gt;amantha!&lt;/span&gt;,那么浏览器基本上向读者呈现等价物。除了遍历所有 .assignedNodes 并将插槽内容替换为插槽之外,还有其他方法可以获得此输出吗?像.slotRenderedInnerHTML这样的东西?

(更新:我现在已经编写了确实遍历 assignedNodes 并做我想做的代码,但与浏览器原生解决方案相比,它看起来脆弱且缓慢。)

class HelloThere extends HTMLElement {
   constructor() {
      super();
      const shadow = this.attachShadow({mode: 'open'});
      shadow.innerHTML = '<span>Hello <slot></slot>!</span>';
   }
}

customElements.define('hello-there', HelloThere);
<hello-there><b>S</b>amantha</hello-there>
<div>Output: <input type="text" size="200" id="output"></input></div>
<script>
const ht = document.querySelector('hello-there');
const out = document.querySelector('#output');

</script>
<button onclick="out.value = ht.innerHTML">InnerHTML hello-there</button><br>
<button onclick="out.value = ht.outerHTML">OuterHTML hello-there</button><br>
<button onclick="out.value = ht.shadowRoot.innerHTML">InnerHTML hello-there shadow</button><br>
<button onclick="out.value = ht.shadowRoot.outerHTML">OuterHTML hello-there shadow (property does not exist)</button><br>
<button onclick="out.value = '<span>Hello <b>S</b>amantha!</span>'">Desired output</button>

【问题讨论】:

  • ht.shadowRoot.innerHTML 你要买什么?为什么需要删除 slot 标签?
  • 请按 StackOverflow 编辑器中的 [ <> ] 按钮并创建可执行文件 SO sn-p。
  • @Danny'365CSI'Engelman——完成了。
  • @connexo——我正在查看带有在浏览器中显示的插槽标签的输出。
  • 再一次:为什么你需要移除插槽标签?你得到的结果您的浏览器呈现的内容。这就是时隙机制的工作原理。

标签: web-component shadow-dom custom-element lit


【解决方案1】:

由于似乎没有浏览器原生的方式来回答这个问题(而且浏览器开发人员似乎并不完全理解看到与用户在浏览器中大致看到的内容非常接近的效用)我写了这个代码。

打字稿在这里,在 sn-ps 中使用纯 Javascript:

const MATCH_END = /(</[a-zA-Z][a-zA-Z0-9_-]*>)$/;

/**
 * Reconstruct the innerHTML of a shadow element
 */
export function reconstruct_shadow_slot_innerHTML(el: HTMLElement): string {
    return reconstruct_shadow_slotted(el).join('').replace(/s+/, ' ');
}

export function reconstruct_shadow_slotted(el: Element): string[] {
    const child_nodes = el.shadowRoot ? el.shadowRoot.childNodes : el.childNodes;
    return reconstruct_from_nodeList(child_nodes);
}

function reconstruct_from_nodeList(child_nodes: NodeList|Node[]): string[] {
    const new_values = [];
    for (const child_node of Array.from(child_nodes)) {
        if (!(child_node instanceof Element)) {
            if (child_node.nodeType === Node.TEXT_NODE) {
                // text nodes are typed as Text or CharacterData in TypeScript
                new_values.push((child_node as Text).data);
            } else if (child_node.nodeType === Node.COMMENT_NODE) {
                const new_data = (child_node as Text).data;
                new_values.push('<!--' + new_data + '-->');
            }
            continue;
        } else if (child_node.tagName === 'SLOT') {
            const slot = child_node as HTMLSlotElement;
            new_values.push(...reconstruct_from_nodeList(slot.assignedNodes()));
            continue;
        } else if (child_node.shadowRoot) {
            new_values.push(...reconstruct_shadow_slotted(child_node));
            continue;
        }
        let start_tag: string = '';
        let end_tag: string = '';

        // see @syduki's answer to my Q at
        // https://stackoverflow.com/questions/66618519/getting-the-full-html-for-an-element-excluding-innerhtml
        // for why cloning the Node is much faster than doing innerHTML;
        const clone = child_node.cloneNode() as Element;  // shallow clone
        const tag_only = clone.outerHTML;
        const match = MATCH_END.exec(tag_only);
        if (match === null) {  // empty tag, like <input>
            start_tag = tag_only;
        } else {
            end_tag = match[1];
            start_tag = tag_only.replace(end_tag, '');
        }
        new_values.push(start_tag);
        const inner_values: string[] = reconstruct_from_nodeList(child_node.childNodes);
        new_values.push(...inner_values);
        new_values.push(end_tag);
    }
    return new_values;
}

在上下文中回答:

const MATCH_END = /(</[a-zA-Z][a-zA-Z0-9_-]*>)$/;


/**
 * Reconstruct the innerHTML of a shadow element
 */
function reconstruct_shadow_slot_innerHTML(el) {
    return reconstruct_shadow_slotted(el).join('').replace(/s+/, ' ');
}

function reconstruct_shadow_slotted(el) {
    const child_nodes = el.shadowRoot ? el.shadowRoot.childNodes : el.childNodes;
    return reconstruct_from_nodeList(child_nodes);
}


function reconstruct_from_nodeList(child_nodes) {
    const new_values = [];
    for (const child_node of Array.from(child_nodes)) {
        if (!(child_node instanceof Element)) {
            if (child_node.nodeType === Node.TEXT_NODE) {
                new_values.push(child_node.data);
            } else if (child_node.nodeType === Node.COMMENT_NODE) {
                const new_data = child_node.data;
                new_values.push('<!--' + new_data + '-->');
            }
            continue;
        } else if (child_node.tagName === 'SLOT') {
            const slot = child_node;
            new_values.push(...reconstruct_from_nodeList(slot.assignedNodes()));
            continue;
        } else if (child_node.shadowRoot) {
            new_values.push(...reconstruct_shadow_slotted(child_node));
            continue;
        }
        let start_tag = '';
        let end_tag = '';

        const clone = child_node.cloneNode();
        // shallow clone
        const tag_only = clone.outerHTML;
        const match = MATCH_END.exec(tag_only);
        if (match === null) {  // empty tag, like <input>
            start_tag = tag_only;
        } else {
            end_tag = match[1];
            start_tag = tag_only.replace(end_tag, '');
        }
        new_values.push(start_tag);
        const inner_values = reconstruct_from_nodeList(child_node.childNodes);
        new_values.push(...inner_values);
        new_values.push(end_tag);
    }

    return new_values;
}

class HelloThere extends HTMLElement {
   constructor() {
      super();
      const shadow = this.attachShadow({mode: 'open'});
      shadow.innerHTML = '<span>Hello <slot></slot>!</span>';
   }
}

customElements.define('hello-there', HelloThere);
<hello-there><b>S</b>amantha</hello-there>
<div>Output: <input type="text" size="200" id="output"></input></div>
<script>
const ht = document.querySelector('hello-there');
const out = document.querySelector('#output');

</script>
<button onclick="out.value = ht.innerHTML">InnerHTML hello-there</button><br>
<button onclick="out.value = ht.outerHTML">OuterHTML hello-there</button><br>
<button onclick="out.value = ht.shadowRoot.innerHTML">InnerHTML hello-there shadow</button><br>
<button onclick="out.value = ht.shadowRoot.outerHTML">OuterHTML hello-there shadow (property does not exist)</button><br>
<button onclick="out.value = reconstruct_shadow_slot_innerHTML(ht)">Desired output</button>

【讨论】:

    猜你喜欢
    • 2018-12-19
    • 1970-01-01
    • 2021-04-17
    • 2012-08-14
    • 2022-12-08
    • 1970-01-01
    • 1970-01-01
    • 2020-05-09
    • 1970-01-01
    相关资源
    最近更新 更多