【问题标题】:Inserting arbitrary HTML into a DocumentFragment将任意 HTML 插入 DocumentFragment
【发布时间】:2012-02-14 21:00:43
【问题描述】:

我知道adding innerHTML to document fragments 最近已被讨论过,并有望被纳入 DOM 标准。但是,在此期间您应该使用什么解决方法?

即取

var html = '<div>x</div><span>y</span>';
var frag = document.createDocumentFragment();

我想要frag 内部的divspan,并使用简单的单行。

没有循环的奖励积分。允许使用 jQuery,但我已经尝试过 $(html).appendTo(frag); frag 之后还是空的。

【问题讨论】:

    标签: javascript jquery documentfragment


    【解决方案1】:

    这是modern browsers 中的一种方式,无需循环:

    var temp = document.createElement('template');
    temp.innerHTML = '<div>x</div><span>y</span>';
    
    var frag = temp.content;
    

    或者,作为可重复使用的

    function fragmentFromString(strHTML) {
        var temp = document.createElement('template');
        temp.innerHTML = strHTML;
        return temp.content;
    }
    

    更新: 我找到了一种更简单的方法来使用 Pete 的主要思想,将 IE11 添加到组合中:

    function fragmentFromString(strHTML) {
        return document.createRange().createContextualFragment(strHTML);
    }
    

    覆盖率优于&lt;template&gt;方法,在IE11、Ch、FF中测试ok。

    提供现场测试/演示http://pagedemos.com/str2fragment/

    【讨论】:

    • IE 根本不支持,所以“现代浏览器”有点误导。
    • 几乎赞成,如果不是演示中的&lt;font&gt; 标签。
    • createContextualFragment 的一个问题是,像 'test' 这样的 html 会忽略 td(并且只创建 'test' 文本节点)。模板标签解决方案是要走的路。 BTW Edge 13 现在支持模板标签。
    • 你有什么好的 Safari 替代方案的想法吗?或者至少他们的追踪器有问题?
    • 我需要来自 Fragment 的元素节点数组,这适用于 Chrome 和 IE11 [].slice.call(temp.content ? temp.content.children : temp.childNodes)
    【解决方案2】:

    目前,仅使用字符串填充文档片段的唯一方法是创建一个临时对象,然后遍历子对象以将它们附加到片段中。

    • 由于它没有附加到文档中,因此不会呈现任何内容,因此不会影响性能。
    • 您会看到一个循环,但它只是循环通过第一个孩子。大多数文档只有几个半根元素,所以这也不是什么大问题。

    如果您想创建整个文档,请改用 DOMParser。看看this answer

    代码:

    var frag = document.createDocumentFragment(),
        tmp = document.createElement('body'), child;
    tmp.innerHTML = '<div>x</div><span>y</span>';
    while (child = tmp.firstElementChild) {
        frag.appendChild(child);
    }
    

    单行(两行便于阅读)(输入:String html,输出:DocumentFragment frag):

    var frag =document.createDocumentFragment(), t=document.createElement('body'), c;
    t.innerHTML = html; while(c=t.firstElementChild) frag.appendChild(c);
    

    【讨论】:

    • Bleh,我想这是唯一的出路。至少您实际上回答了问题并满足了问题陈述:)。尽管如此,不得不循环还是很烦人。嗯。
    • 缺少元素之间的文本.... var elemQueried = document.createDocumentFragment(); var tmp = document.createElement('body'), child; tmp.innerHTML = '&lt;div&gt;x&lt;/div&gt;Bleh&lt;span&gt;y&lt;/span&gt;'; var children = tmp.childNodes; while(children.length){ elemQueried.appendChild(children[0]); }
    • @PAEz 在下面发布了解决此问题的答案。
    【解决方案3】:

    使用Range.createContextualFragment:

    var html = '<div>x</div><span>y</span>';
    var range = document.createRange();
    // or whatever context the fragment is to be evaluated in.
    var parseContext = document.body; 
    range.selectNodeContents(parseContext);
    var fragment = range.createContextualFragment(html);
    

    请注意,此方法与&lt;template&gt; 方法之间的主要区别是:

    • Range.createContextualFragment 得到了更广泛的支持(IE11 刚刚得到它,Safari、Chrome 和 FF 已经有一段时间了)。

    • HTML 中的自定义元素将立即与范围一起升级,但仅当克隆到带有模板的真实文档时。模板方法更“惰性”,这可能是可取的。

    【讨论】:

    • 这应该是选择的答案,值得更多的支持
    • 小心!未标准化。
    • 它不适用于tdth 元素的table
    【解决方案4】:

    从来没有人提供过要求的“简单的单行”。

    给定变量……

    var html = '<div>x</div><span>y</span>';
    var frag = document.createDocumentFragment();
    

    …下面这行就可以解决问题(在 Firefox 67.0.4 中):

    frag.append(...new DOMParser().parseFromString(html, "text/html").body.childNodes);
    

    【讨论】:

    • 毫无疑问!这是最好的答案!旧浏览器(包括 IE9)都支持 documentFragments 和 DOMParser。竖起大拇指
    【解决方案5】:

    @PAEz 指出@RobW 的方法不包括文本between 元素。那是因为children 只抓取元素,而不抓取节点。更稳健的方法可能如下:

    var fragment = document.createDocumentFragment(),
        intermediateContainer = document.createElement('div');
    
    intermediateContainer.innerHTML = "Wubba<div>Lubba</div>Dub<span>Dub</span>";
    
    while (intermediateContainer.childNodes.length > 0) {
        fragment.appendChild(intermediateContainer.childNodes[0]);
    }
    

    在较大的 HTML 块上性能可能会受到影响,但是,它与许多旧浏览器兼容,并且简洁。

    【讨论】:

    • 更简洁:.firstChild - while (intermediateContainer.firstChild) fragement.appendChild(intermediateContainer.firstChild);
    • 好建议。我想在这一点上这是一个风格问题。谢谢!
    【解决方案6】:

    createDocumentFragment 创建一个空的 DOM “容器”。 innerHtml 和其他方法仅适用于 DOM 节点(而不是容器),因此您必须先创建节点,然后将它们添加到片段中。您可以使用一种痛苦的 appendChild 方法来完成它,或者您可以创建一个节点并修改它的 innerHtml 并将其添加到您的片段中。

    var frag = document.createDocumentFragment();
        var html = '<div>x</div><span>y</span>';
    var holder = document.createElement("div")
    holder.innerHTML = html
    frag.appendChild(holder)
    

    使用 jquery,您只需将 html 保存并构建为字符串。如果要将其转换为 jquery 对象以对其执行类似 jquery 的操作,只需执行 $(html) 即可在内存中创建一个 jquery 对象。一旦你准备好附加它,你只需将它附加到页面上的现有元素

    【讨论】:

    • 是的,我不想要额外的容器元素。
    • 当你追加时,你总是需要一个“额外的”(或者我应该说是一个目标)容器——是一个主体或其他一些标签。如果你不希望它是你的循环
    • 我想我希望有一种方法可以一次性将holder 的所有子代复制到frag(不循环)。 appendChildren 什么的。但听起来好像不存在这样的方法。
    • @Domenic 这样的方法存在......这就是文档片段的全部意义;-) 你真正的问题是你正在处理一个 html 字符串,而不是 html 元素。
    • @Domenic frag = holder.cloneNode(true);
    【解决方案7】:

    就像@dandavis 说的,有一种使用模板标签的标准方法。
    但是如果你喜欢支持 IE11 并且需要解析像'

    test'这样的表格元素,你可以使用这个函数:
    function createFragment(html){
        var tmpl = document.createElement('template');
        tmpl.innerHTML = html;
        if (tmpl.content == void 0){ // ie11
            var fragment = document.createDocumentFragment();
            var isTableEl = /^[^\S]*?<(t(?:head|body|foot|r|d|h))/i.test(html);
            tmpl.innerHTML = isTableEl ? '<table>'+html : html;
            var els        = isTableEl ? tmpl.querySelector(RegExp.$1).parentNode.childNodes : tmpl.childNodes;
            while(els[0]) fragment.appendChild(els[0]);
            return fragment;
        }
        return tmpl.content;
    }
    

    【讨论】:

    • Plus 用于在 IE 中支持 td。
    【解决方案8】:

    这是一个 x-browser 解决方案,在 IE10、IE11、Edge、Chrome 和 FF 上进行了测试。

        function HTML2DocumentFragment(markup: string) {
            if (markup.toLowerCase().trim().indexOf('<!doctype') === 0) {
                let doc = document.implementation.createHTMLDocument("");
                doc.documentElement.innerHTML = markup;
                return doc;
            } else if ('content' in document.createElement('template')) {
                // Template tag exists!
                let el = document.createElement('template');
                el.innerHTML = markup;
                return el.content;
            } else {
                // Template tag doesn't exist!
                var docfrag = document.createDocumentFragment();
                let el = document.createElement('body');
                el.innerHTML = markup;
                for (let i = 0; 0 < el.childNodes.length;) {
                    docfrag.appendChild(el.childNodes[i]);
                }
                return docfrag;
            }
        }
    

    【讨论】:

      【解决方案9】:

      我会选择这样的..

      function fragmentFromString(html) {
        const range = new Range();
        const template = range.createContextualFragment(html);
        range.selectNode(template.firstElementChild);
        return range;
      }
      
      // Append to body
      // document.body.append(fragmentFromString(`<div>a</div>`).cloneContents())
      

      通过这种方式,您可以将内容保存在 Range 对象中,并免费获得所有需要的方法。

      你可以在这里找到所有 Range 方法和属性的列表https://developer.mozilla.org/en-US/docs/Web/API/Range

      注意:记住使用detatch() 方法后,避免泄漏并提高性能。

      【讨论】:

        【解决方案10】:
        var html = '<div>x</div><span>y</span>';
        var frag = document.createDocumentFragment();
        var e = document.createElement('i');
        frag.appendChild(e);
        e.insertAdjacentHTML('afterend', html);
        frag.removeChild(e);
        

        【讨论】:

        • 这不起作用(至少在 Chrome 中)。在 DocumentFragment 中使用 insertAdjacentHTML 时,您将收到此错误:Uncaught DOMException: Failed to execute 'insertAdjacentHTML' on 'Element': The element has no parent. 另外,addChild 不是方法,您应该使用 appendChild
        【解决方案11】:

        要使用尽可能少的行来做到这一点,您可以将上面的内容包装在另一个 div 中,这样您就不必多次循环或调用 appendchild。使用 jQuery(正如您提到的那样),您可以非常快速地创建一个未附加的 dom 节点并将其放置在片段中。

        var html = '<div id="main"><div>x</div><span>y</span></div>';
        var frag = document.createDocumentFragment();
        frag.appendChild($​(html)[0]);
        

        【讨论】:

        • @RobW。如果 OP 写了 " jQuery is allowed",这不是拒绝投票的好理由
        • @gdoron 我对答案投反对票的主要原因是文字解释是 100% 错误的。 DocumentFragment 对象可以有任意数量的子元素。我之前的评论是指摩根在 Michal 的回答中的评论。
        • @RobW 这只是一个有趣的评论,因为我们是在同一时间发布的。我可以看到其他人如何认为它不合适。我已经删除了它。我也明白你对我的逻辑的意思。在我的测试过程中,我犯了一个错误,并以此为基础进行了解释。我已经更新了我的答案。谢谢
        • 是的,与 Michal 的回答相同;这不满足问题陈述,因为它插入了一个额外的包装器div#main
        猜你喜欢
        • 2013-11-24
        • 2017-01-04
        • 1970-01-01
        • 1970-01-01
        • 2020-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多