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

IT技术 javascript dom prototypejs
2021-01-28 05:28:45

我有一个表示元素的 HTML 字符串:'<li>text</li>'. 我想将它附加到 DOM 中的一个元素(ul在我的例子中是一个)。我如何使用 Prototype 或 DOM 方法做到这一点?

(我知道我可以在 jQuery 中轻松做到这一点,但不幸的是我们没有使用 jQuery。)

6个回答

注意:当前大多数浏览器都支持 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 模板不同,这不适用于某些不能合法成为 a 子元素的元素<div>,例如<td>s。

如果您已经在使用库,我建议您坚持使用库批准的从 HTML 字符串创建元素的方法:

谢谢,<div>我添加的 HTML 包装让我.innerHTML很烦。我从没想过使用.firstChild.
2021-03-10 05:28:45
请注意,顺便说一下,这不适用于脚本标签。使用添加到 DOM 的脚本标签innerHTML将不会被执行。对于这些情况,最好使用var script = document.createElement('script'), 然后使用script.srcscript.textContent取决于脚本是否内联。然后,添加脚本document.body.appendChild(script)
2021-03-15 05:28:45
如何在不使用这种不安全的innerHTML 赋值的情况下使用jQuery 将innerHTML 设置为创建的div(div.innerHTML = "some value")
2021-03-16 05:28:45
我会使用 firstElementChild 而不是 firstChild (参见w3schools.com/jsref/prop_element_firstelementchild.asp),因为如果模板的前面或末尾有空格, firstChild 将返回空的 textNode
2021-03-17 05:28:45
函数名称createElementFromHTML 具有误导性,因为div.firstChild返回的Node不是HTMLElementeg 不能node.setAttribute改为从函数创建Element返回div.firstElementChild
2021-03-28 05:28:45

HTML 5 引入了<template>可用于此目的元素(如现在在WhatWG 规范MDN 文档中所述)。

<template>元件用于声明可以在脚本中使用的HTML的片段。元素在 DOM 中表示为HTMLTemplateElement具有类型.content属性的a DocumentFragment,以提供对模板内容的访问。这意味着,你可以通过设置一个HTML字符串转换为DOM元素innerHTMLa的<template>元素,然后伸到template.content财产。

例子:

/**
 * @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>');

请注意,使用不同容器元素(例如 a)的div类似方法不太有效。HTML 对哪些元素类型允许存在于哪些其他元素类型中进行了限制;例如,您不能将 atd作为 a 的直接子代div如果您尝试设置innerHTMLadiv以包含它们,这会导致这些元素消失由于<template>s 对它们的内容没有这样的限制,所以在使用模板时这个缺点不适用。

但是,template在某些旧浏览器中不支持。截至 2021 年 4 月,Can I use...估计全球 96% 的用户正在使用支持templates的浏览器特别是,没有任何版本的 Internet Explorer 支持它们;Microsofttemplate直到 Edge 发布才实施支持。

如果您有幸编写仅针对现代浏览器用户的代码,请立即使用它们。否则,您可能需要等待一段时间才能让用户跟上。

这是一种有效的方法并且非常干净;但是,(至少在 Chrome 50 中)这会破坏脚本标记处理。换句话说,使用此方法创建脚本标记然后将其附加到文档(正文或头部)不会导致对标记进行评估,从而阻止执行脚本。(如果评估发生在附加上,这可能是设计使然;我不能肯定地说。)
2021-03-13 05:28:45
@Dai 确实没有,因为没有这样的属性。你误读了我的回答;我设置了innerHTML模板本身的(这是一个Element),而不是它的.content(这是一个DocumentFragment)。
2021-03-23 05:28:45
迷人的!您甚至可以通过执行以下操作来查询元素: template.content.querySelector("img");
2021-03-27 05:28:45
在 MDN 上没有看到innerHTML定义为 DOM 片段对象的属性(来自<template>.content):developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
2021-04-03 05:28:45
它有一个问题。如果您在开始或结束时有一个带有空格的标签(!),它们将被删除!不要误会我的意思,这不是关于html.trim通过innerHTML 设置的内部解析删除的空格在我的情况下,它删除了作为 textNode 一部分的重要空格。:-(
2021-04-08 05:28:45

使用insertAdjacentHTML()它适用于所有当前浏览器,甚至适用于 IE11。

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

这具有出色的浏览器支持,并且非常简单。创建一个虚拟元素只是为了获取它的子节点并没有什么奇怪的。
2021-03-17 05:28:45
第二,太棒了!与此相比的一大优势innerHTML += ...是使用此方法对先前元素的引用仍然完好无损。
2021-03-18 05:28:45
首先,insertAdjacentHTML适用于自 IE 4.0 以来的所有浏览器。
2021-03-19 05:28:45
第三,第一个参数的可能值是:beforebegin, afterbegin, beforeend, afterend请参阅 MDN 文章。
2021-03-26 05:28:45
太糟糕了,它不会将插入的 HTML 作为元素返回
2021-04-05 05:28:45

无需任何调整,您就有了一个原生 API:

const toNodes = html =>
    new DOMParser().parseFromString(html, 'text/html').body.childNodes[0]
-1 因为这是先前答案的重复,该答案明确指出了我在上面的评论中提到的缺点。
2021-03-19 05:28:45
这与已接受的答案存在相同的主要缺点 - 它会像<td>text</td>. 这是因为DOMParser正在尝试解析完整的HTML 文档,并且并非所有元素都作为文档的根元素有效。
2021-03-23 05:28:45
不能通过包装在 HTML 根元素中并提取 firstChild 来修改它以使用片段吗?
2021-03-24 05:28:45
@MarkAmery 没关系。这个答案和语法的形状与我们今天所看到的一样。如果它展示了一种更现代的写作方式,那么它永远不会是一个糟糕的重复。
2021-03-25 05:28:45

较新的 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
“有报道称桌面 Safari 曾一度支持 Range.createContextualFragment(),但至少在 Safari 9.0 和 9.1 中不受支持。” (答案中的 MDN 链接)
2021-03-12 05:28:45
请注意,这与设置innerHTMLdiv有类似的缺点某些元素,例如tds,将被忽略并且不会出现在结果片段中。
2021-04-01 05:28:45