使用内置的DOM方法或Prototype从HTML字符串创建新的DOM元素

923
我有一个表示元素的HTML字符串:'<li>text</li>',我想将其附加到DOM中的一个元素(在我的情况下是一个ul)。我该如何使用Prototype或DOM方法来实现这一点?(我知道在jQuery中很容易做到这一点,但不幸的是我们没有使用jQuery。)

2
抱歉,您究竟想要实现什么目标?将HTML字符串转换为DOM元素? - Crescent Fresh
86
这些解决方案过于间接,这让人感到不幸。我希望标准委员会能够指定类似的规范,例如:“var nodes = document.fromString('<b>Hello</b> <br>');” - Sridhar Sarnobat
1
我遇到了一个问题,因为我有一些需要传递的属性,而我不想解析它们:"<table id='5ccf9305-4b10-aec6-3c55-a6d218bfb107' class='data-table row-border display' cellspacing='0' width='100%'></table>"所以,我只是使用了:$("<table id='5ccf9305-4b10-aec6-3c55-a6d218bfb107' class='data-table row-border display' cellspacing='0' width='100%'></table>") - stevenlacerda
30个回答

1083

注意:大多数现代浏览器支持 HTML <template> 元素,这为从字符串创建元素提供了更可靠的方法。有关详细信息,请参见下面 Mark Amery 的答案

对于旧版浏览器和 node/jsdom(在撰写本文时尚未支持 <template> 元素),请使用以下方法。这与库所使用的获取来自 HTML 字符串的 DOM 元素相同(针对 IE 进行了一些额外的工作以解决其 innerHTML 实现中的错误):

function createElementFromHTML(htmlString) {
  var div = document.createElement('div');
  div.innerHTML = htmlString.trim();

  // Change this to div.childNodes to support multiple top-level nodes.
  return div.firstChild;
}

请注意,与HTML模板不同,这种方法对于一些不能合法成为
子元素的元素(例如)是无效的。
如果您已经在使用库,则建议您坚持使用库批准的从HTML字符串创建元素的方法:
Prototype已将此功能内置到其update()方法中。
jQuery在其jQuery(html)jQuery.parseHTML方法中实现了它。

1
如何使用jQuery将innerHTML设置为创建的div,而不使用不安全的innerHTML赋值(div.innerHTML =“some value”) - Shivanshu Goyal
46
createElementFromHTML这个函数名起得有误导性,因为div.firstChild返回的是一个Node而非HTMLElement,例如无法使用node.setAttribute。如果要创建一个Element,应该从函数中返回div.firstElementChild - Semmel
我正在尝试解析创建的div内部的SVG,输出结果是[object SVGSVGElement],而控制台日志却给出了正确的DOM元素。我做错了什么? - ed1nh0
5
顺便提一句,这种方法不适用于脚本标签。使用 innerHTML 添加到 DOM 中的脚本标签将不会被执行。对于这些情况,最好使用 var script = document.createElement('script'),然后根据脚本是否内联使用 script.srcscript.textContent。然后,使用 document.body.appendChild(script) 添加脚本。 - ethan.roday
4
建议使用 firstElementChild 替代 firstChild(参见 https://www.w3schools.com/jsref/prop_element_firstelementchild.asp),因为如果模板的前面或末尾有空格,firstChild 将返回空的文本节点。 - Chris Panayotoff
显示剩余2条评论

668
HTML 5引入了<template>元素,可用于此目的(现在已在WhatWG规范MDN文档中描述)。
一个 <template> 元素用于声明可在脚本中使用的 HTML 片段。该元素在 DOM 中表示为 HTMLTemplateElement,它具有 .content 属性,类型为 DocumentFragment,以提供对模板内容的访问。这意味着您可以通过设置 <template> 元素的 innerHTML,然后进入 template.content 属性,将 HTML 字符串转换为 DOM 元素。
示例:
/**
 * @param {String} HTML representing a single element
 * @return {Element}
 */
function htmlToElement(html) {
    var template = document.createElement('template');
    html = html.trim(); // Never return a text node of whitespace as the result
    template.innerHTML = html;
    return template.content.firstChild;
}

var td = htmlToElement('<td>foo</td>'),
    div = htmlToElement('<div><span>nested</span> <span>stuff</span></div>');

/**
 * @param {String} HTML representing any number of sibling elements
 * @return {NodeList} 
 */
function htmlToElements(html) {
    var template = document.createElement('template');
    template.innerHTML = html;
    return template.content.childNodes;
}

var rows = htmlToElements('<tr><td>foo</td></tr><tr><td>bar</td></tr>');

请注意,类似的方法使用不同的容器元素,如div并不太可行。HTML对于哪些元素类型可以存在于其他元素类型内部有限制;例如,您不能将td作为div的直接子元素。如果您尝试设置divinnerHTML以包含它们,则会导致这些元素消失。由于<template>没有对其内容施加此类限制,因此在使用模板时不会出现这种缺陷。
然而,一些旧版浏览器不支持template。截至2021年4月,《Can I use...》估计全球96%的用户正在使用支持template的浏览器。特别是,任何版本的Internet Explorer都不支持它们;直到Edge发布之前,Microsoft才实现了template支持。
如果你很幸运只需要为现代浏览器用户编写代码,那么现在就可以使用它们。否则,你可能需要等待一段时间让用户跟上。

3
这是一种有效的方法,非常简洁明了;然而,在Chrome 50中,它会破坏脚本标签的处理。换句话说,使用此方法创建一个脚本标签,然后将其附加到文档(body或head)中,不会导致标签被评估,因此阻止了脚本的执行。(如果评估发生在附加时,则可能是有意设计的;我无法确定。) - shanef22
2
太棒了!你甚至可以通过以下方式查询元素: template.content.querySelector("img"); - Roger Gajraj
5
这里有一个问题。如果你在标签的开头或结尾处有带空格的标签,它们将被删除!别误会,这不是关于html.trim去除空格的问题,而是由于innerHTML设置的内部解析。在我的情况下,它会删除作为textNode一部分的重要空格。 :-( - e-motiv
2
可以使用template.content.firstElementChild来替代去除HTML。 - asynts
1
@vatavale 我猜你可以使用 htmlToElements,但将最后一行改为 return template.content.childNodes.length == 1 ? template.content.firstChild : template.content.childNodes;,但我不建议这样做;对我来说,有一个通常返回集合但有时返回单个项的函数似乎很奇怪。 - Mark Amery
显示剩余8条评论

240

使用insertAdjacentHTML()方法。该方法适用于所有现代浏览器,包括IE11。

var mylist = document.getElementById('mylist');
mylist.insertAdjacentHTML('beforeend', '<li>third</li>');
<ul id="mylist">
 <li>first</li>
 <li>second</li>
</ul>


19
首先,insertAdjacentHTML 可以在IE 4.0及以上版本的所有浏览器中使用。 - Jonas Äppelgran
11
第二,这很棒!与使用“innerHTML += ...”相比,使用此方法的一个重要优势是之前元素的引用仍然存在。 - Jonas Äppelgran
17
第三点,第一个参数可能的取值包括:beforebeginafterbeginbeforeendafterend。请参见 MDN 文章。 - Jonas Äppelgran
11
很遗憾它不会将插入的HTML作为元素返回。 - Mojimi
3
这个功能在浏览器中得到了很好的支持,而且非常简单明了。不需要创建一个虚拟元素来获取其子节点,这样做并不奇怪。 - DylanReile
显示剩余2条评论

49

无需进行任何微调,您已经拥有本地API:

const toNodes = html =>
    new DOMParser().parseFromString(html, 'text/html').body.childNodes[0]

18
这与被接受的答案面临相同的主要问题 - 它会破坏像 <td>文本</td> 这样的 HTML。这是因为 DOMParser 试图解析完整的 HTML 文档,而并非所有元素都有效作为文档的根元素。 - Mark Amery
7
因为这是一个重复的答案,与我在评论中提到的缺点相同,因此被标记为-1分。早期答案链接 - Mark Amery
1
@MarkAmery 这并不重要。这个答案和语法是按照我们今天习惯看到的形式来塑造的。如果它展示了一种更现代的编写方式,那么它永远不会是一个坏的副本。 - vdegenne
这个能不能通过包装在HTML根元素中并拔取第一个子元素来修改以适用于片段? - Jonathan
当然,但这样一来,你就基本上取消了使用DOMParser的任何理由。只需创建一个模板,设置innerHTML属性,然后克隆节点。这样你就不会因为拼写错误的MIME类型而浪费一个小时来弄清楚为什么事情不起作用,因为你一直忽略了"text/html"。 - undefined

40
对于一些html片段,如<td>test</td>,div.innerHTML,DOMParser.parseFromString和range.createContextualFragment(没有正确的上下文),其他答案中提到的解决方案无法创建<td>元素。
jQuery.parseHTML()可以正确处理它们(我将 jQuery 2的parseHTML函数提取为独立函数,可用于非jquery代码库)。
如果您只支持Edge 13+,则使用HTML5模板标记更简单:
function parseHTML(html) {
    var t = document.createElement('template');
    t.innerHTML = html;
    return t.content;
}

var documentFragment = parseHTML('<td>Test</td>');

34

新的DOM实现具有range.createContextualFragment,以框架无关的方式实现了你想要的功能。

它得到了广泛的支持。但是,为确保,请在同一MDN链接中向下检查其兼容性,因为它将会发生变化。截至2017年5月,这就是它的情况:

Feature         Chrome   Edge   Firefox(Gecko)  Internet Explorer   Opera   Safari
Basic support   (Yes)    (Yes)  (Yes)           11                  15.0    9.1.2

4
需要注意的是,这种方法与将 innerHTML 应用于 div 的方法存在类似的缺点;例如,某些元素(如 td)将被忽略,因此不会出现在生成的分段中。 - Mark Amery
有报告称桌面版Safari曾经支持Range.createContextualFragment(),但至少在Safari 9.0和9.1中不再支持。(MDN链接在答案中) - akauppi

16

您可以使用以下方法从字符串创建有效的DOM节点:

document.createRange().createContextualFragment()

以下示例将一个按钮元素添加到页面中,该元素是从一个字符串中提取的标记:

let html = '<button type="button">Click Me!</button>';
let fragmentFromString = function (strHTML) {
  return document.createRange().createContextualFragment(strHTML);
}
let fragment = fragmentFromString(html);
document.body.appendChild(fragment);


8
尽管这会创建一个“DocumentFragment”对象,但令人惊讶的是,它仍然存在与接受的答案相同的缺陷:如果你执行“document.createRange().createContextualFragment('<td>bla</td>')”,你得到的片段只包含文本“bla”,而没有<td>元素。至少在Chrome 63中是这样的,我还没有深入研究规范来确定这是否是正确的行为。 - Mark Amery
一个稍微简短的版本是 new Range().createContextualFragment(htmlString) - mdre
据我所知,历史上存在一些DOM元素,它们不能作为独立的节点存在,无论是在片段中还是其他情况下。它们只能作为其他元素的子元素有效,因此如果你试图单独创建它们,是行不通的。 - undefined

16

以下是一个简单的方法:

String.prototype.toDOM=function(){
  var d=document
     ,i
     ,a=d.createElement("div")
     ,b=d.createDocumentFragment();
  a.innerHTML=this;
  while(i=a.firstChild)b.appendChild(i);
  return b;
};

var foo="<img src='//placekitten.com/100/100'>foo<i>bar</i>".toDOM();
document.body.appendChild(foo);

5
这里的区别在于他使用一个片段(fragment)来允许在DOM中添加多个根元素,这是一个额外的好处。如果William在他的回答中提到了这一点就好了... - Koen.

11

我正在使用这种方法(适用于IE9+),尽管它无法解析<td>或其他一些无效的body子元素:

function stringToEl(string) {
    var parser = new DOMParser(),
        content = 'text/html',
        DOM = parser.parseFromString(string, content);

    // return element
    return DOM.body.childNodes[0];
}

stringToEl('<li>text</li>'); //OUTPUT: <li>text</li>

11

我添加了一个Document原型,可以从字符串创建一个元素:

Document.prototype.createElementFromString = function (str) {
   const element = new DOMParser().parseFromString(str, 'text/html');
   const child = element.documentElement.querySelector('body').firstChild;
   return child;
};

使用方法:

document.createElementFromString("<h1>Hello World!</h1>");

1
请注意,正如此页面其他地方指出的那样,这对于td不起作用。 - Mark Amery
解析“<table><tr>”+ tdRowArr.join(“”) + “</tr></table>”,然后移动TD节点并不难,类似于他从上面的body标签中提取实际节点的方式。 - Christoffer Bubach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接