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

Ome*_*ari 555 javascript dom prototypejs

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

(我知道我可以在jQuery中轻松完成这个,但不幸的是我们没有使用jQuery.)

Cre*_*esh 757

注意:大多数当前浏览器都支持HTML <template>元素,这提供了一种更可靠的方法来转换从字符串创建元素.有关详细信息,请参阅Mark Amery的答案.

对于较旧的浏览器和node/jsdom :( <template>在编写时尚不支持元素),请使用以下方法.图书馆用来从HTML字符串中获取DOM元素也是一样的(IE的一些额外工作可以解决bug的实现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; 
}
Run Code Online (Sandbox Code Playgroud)

请注意,与HTML模板这不会对某些元素不能合法的儿童工作<div>,如<td>秒.

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

  • 函数名称`createElementFromHTML`是误导性的,因为`div.firstChild`返回一个`Node`,它不是`HTMLElement`,例如不能`node.setAttribute`.要创建一个`Element`,请从函数返回`div.firstElementChild`. (8认同)
  • 顺便请注意,这**不适用于脚本标签。使用“innerHTML”添加到 DOM 的脚本标签将不会被执行。对于这些情况,最好使用 `var script = document.createElement('script')`,然后根据脚本是否内联使用 `script.src` 或 `script.textContent`。然后,使用“document.body.appendChild(script)”添加脚本。 (5认同)
  • 我会使用 firstElementChild 而不是 firstChild (参见 https://www.w3schools.com/jsref/prop_element_firstelementchild.asp ),因为如果模板的前面或末尾有空格, firstChild 将返回空 textNode (3认同)

Mar*_*ery 288

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

A <template>是一个HTML元素,允许任何其他元素类型作为子元素.该template有一个.content,你可以用JavaScript,它指向一个访问属性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>');
Run Code Online (Sandbox Code Playgroud)

请注意,使用不同容器元素的div类似方法(例如a)不太起作用.HTML限制了允许哪些元素类型存在于哪些元素类型中; 例如,你不能把a td作为直接的孩子div.如果您尝试设置innerHTMLa div来包含它们,这会导致这些元素消失.由于<template>s对其内容没有这样的限制,因此在使用模板时这个缺点不适用.

但是,template某些旧版浏览器不支持.截至2018年1月,我可以使用...估计全球90%的用户正在使用支持templates 的浏览器.特别是,没有版本的Internet Explorer支持它们; template在Edge发布之前,Microsoft没有实现支持.

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

  • 好的,现代的方法.在Opera上测试过,让我们忘记IE吧 (3认同)
  • 这是一种有效的方法,非常干净;但是,(至少在Chrome 50中),这会中断脚本标签的处理。换句话说,使用此方法创建脚本标签,然后将其附加到文档(正文或头部)不会导致对标签进行评估,因此会阻止脚本执行。(这可能是设计使然,如果在附件上进行评估;我不能肯定地说。) (2认同)
  • 迷人的!您甚至可以通过执行以下操作来查询元素: template.content.querySelector("img"); (2认同)
  • 有一个问题。如果您在其中有一个标签,在标签的开始或结尾处带有空格(!),则它们将被删除!不要误会我的意思,这不是关于html.trim删除的空格,而是关于innerHTML设置的内部解析。就我而言,它删除了textNode中重要的空间。:-( (2认同)
  • 可以使用“template.content.firstElementChild”来代替剥离 html。 (2认同)

Chr*_*use 89

使用insertAdjacentHTML().它适用于所有当前的浏览器,即使使用IE11.

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

  • 首先,`insertAdjacentHTML`适用于IE 4.0以来的所有浏览器. (8认同)
  • 第三,第一个参数的可能值是:`beforebegin`,`afterbegin`,`beforeend`,`afterend`.请参阅MDN文章. (6认同)
  • 第二,它太棒了!与"innerHTML + = ..."相比,一个很大的优点是使用此方法对先前元素的引用仍然完好无损. (5认同)
  • 它具有出色的浏览器支持并且完全简单。创建一个虚拟元素只是为了获取其子节点,这并不奇怪。 (3认同)
  • 太糟糕了,它没有将插入的HTML作为元素返回 (2认同)
  • 这个答案应该就是这个,它非常完美! (2认同)

koj*_*iro 26

较新的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
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这与设置div的`innerHTML`有类似的缺点; 某些元素,如`td`s,将被忽略,不会出现在结果片段中. (3认同)

mat*_*001 18

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

const toNodes = html =>
    new DOMParser().parseFromString(html, 'text/html').body.childNodes
Run Code Online (Sandbox Code Playgroud)

  • 这会遇到与接受的答案相同的主要缺点 - 它会像`<td> text </ td>`那样破坏HTML.这是因为`DOMParser`试图解析一个完整的*HTML文档*,而不是所有元素都作为文档的根元素有效. (5认同)
  • -1,因为它是[以前的答案](/sf/answers/2639193301/)的重复,后者明确指出了我在上面的评论中提到的缺点。 (3认同)

wil*_*alo 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);
Run Code Online (Sandbox Code Playgroud)

  • @MarkAmery这里的区别在于他使用片段允许在DOM中附加多个根元素,这是一个额外的好处.如果只有威廉提到这是他的答案...... (4认同)

Mun*_*war 15

对于某些html片段,例如<td>test</td>div.innerHTML,DOMParser.parseFromString和range.createContextualFragment(没有正确的上下文)将不会创建<td>元素.

jQuery.parseHTML()处理它们(我将jQuery 2的parseHTML函数提取到一个独立的要点).

对于Edge 13+,请使用模板标记:

function parseHTML(html) {
    var t = document.createElement('template');
    t.innerHTML = html;
    return t.content.cloneNode(true);
}

var documentFragment = parseHTML('<td>Test</td>');
Run Code Online (Sandbox Code Playgroud)


Raz*_*aza 11

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

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

用法:

document.createElementFromString("<h1>Hello World!</h1>");
Run Code Online (Sandbox Code Playgroud)


Pab*_*icz 9

使用Prototype,您还可以:

HTML:

<ul id="mylist"></ul>
Run Code Online (Sandbox Code Playgroud)

JS:

$('mylist').insert('<li>text</li>');
Run Code Online (Sandbox Code Playgroud)

  • @JuanHurtado 这是使用 PrototypeJs 作为大约 11 年前请求的 OP ;) (2认同)

Gib*_*boK 8

您可以使用以下命令从字符串创建有效的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);
Run Code Online (Sandbox Code Playgroud)

  • 尽管这会创建一个`DocumentFragment`对象,但它仍然*令我惊讶 - 遭受与接受的答案相同的缺陷:如果你执行`document.createRange().createContextualFragment('<td> bla </ td >')`,你得到的片段只包含没有`<td>`元素的文本'bla'.或者至少,这是我在Chrome 63中观察到的; 我没有深入研究规范,以确定它是否是*正确*行为. (3认同)

小智 7

为什么不用原生js呢?

    var s="<span class='text-muted' style='font-size:.75em; position:absolute; bottom:3px; left:30px'>From <strong>Dan's Tools</strong></span>"
    var e=document.createElement('div')
    var r=document.createRange();
    r.selectNodeContents(e)
    var f=r.createContextualFragment(s);
    e.appendChild(f);
    e = e.firstElementChild;
Run Code Online (Sandbox Code Playgroud)


Mon*_*tee 6

回答

  • 创建一个 Template
  • Template'sinnerHTML设置为您的string .trim()
  • 创建ArrayTemplate's儿童
  • 返回children, child, 或

function toElement(s='',c,t=document.createElement('template'),l='length'){
t.innerHTML=s.trim();c=[...t.content.childNodes];return c[l]>1?c:c[0]||'';}



console.log(toElement());
console.log(toElement(''));
console.log(toElement('    '));
console.log(toElement('<td>With td</td>'));
console.log(toElement('<tr><td>With t</td></tr>'));
console.log(toElement('<tr><td>foo</td></tr><tr><td>bar</td></tr>'));
console.log(toElement('<div><span>nested</span> <span>stuff</span></div>'));
Run Code Online (Sandbox Code Playgroud)


usr*_*owe 5

我正在使用此方法(在IE9 +中有效),尽管它不会解析<td>或其他一些无效的身体直接子代:

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>
Run Code Online (Sandbox Code Playgroud)


NVR*_*VRM 5

为了进一步增强我们可以在不同地方找到的有用的.toDOM()片段,我们现在可以安全地使用反引号模板文字)。

所以我们可以在foo html 声明中使用单引号和双引号。

对于那些熟悉这个术语的人来说,这就像heredocs

这可以通过变量进一步增强,以制作复杂的模板:

模板文字由反引号 (`)(重音符号)字符而不是双引号或单引号括起来。模板文字可以包含占位符。这些由美元符号和花括号 (${expression}) 表示。占位符中的表达式和它们之间的文本被传递给一个函数。默认函数只是将这些部分连接成一个字符串。如果在模板文字(此处为标记)之前有一个表达式,则称为“标记模板”。在这种情况下,标记表达式(通常是一个函数)会使用处理过的模板文字被调用,然后您可以在输出之前对其进行操作。要在模板文字中转义反引号,请在反引号之前放置一个反斜杠 \。

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
}

// Using template literals
var a = 10, b = 5
var foo=`
<img 
  onclick="alert('The future starts today!')"   
  src='//placekitten.com/100/100'>
foo${a + b}
  <i>bar</i>
    <hr>`.toDOM();
document.body.appendChild(foo);
Run Code Online (Sandbox Code Playgroud)
img {cursor: crosshair}
Run Code Online (Sandbox Code Playgroud)

那么,为什么不直接使用.innerHTML += 呢?通过这样做,整个 DOM 正在由浏览器重新计算,速度要慢得多。

https://caniuse.com/template-literals

  • 支持模板文字的浏览器不也支持 `&lt;template&gt;` 元素吗?这样会灵活很多。 (2认同)

Rad*_*rán 5

解决方案- 适用于 IE 4.0 以来的所有浏览器

var htmlString = `<body><header class="text-1">Hello World</header><div id="table"><!--TABLE HERE--></div></body>`;
var tableString = `<table class="table"><thead><tr><th>th cell</th></tr></thead><tbody><tr><td>td cell</td></tr></tbody></table>`;


var doc = document.implementation.createHTMLDocument();
    doc.open();
    doc.write(htmlString);
    doc.getElementById('table').insertAdjacentHTML('beforeend', tableString);
    doc.close();

console.log(doc);
Run Code Online (Sandbox Code Playgroud)

或者你可以使用DOMParser

var doc = (new DOMParser).parseFromString(htmlString, "text/html");
    doc.getElementById('table').insertAdjacentHTML('beforeend', tableString);

console.log(doc);
Run Code Online (Sandbox Code Playgroud)