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


621

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

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


抱歉,您到底要完成什么?HTML字符串-> dom元素?

36
不幸的是,这些解决方案是如此间接。我希望标准委员会指定类似的内容:var nodes = document.fromString("<b>Hello</b> <br>");
Sridhar Sarnobat,2015年


1
我对上述内容有疑问,因为我有一些需要传递的属性,而且我不想解析它们:“ <表id ='5ccf9305-4b10-aec6-3c55-a6d218bfb107'class ='data-table行边框显示'cellspacing ='0'width ='100%'> </ table>“因此,我只用了:$(” <表id ='5ccf9305-4b10-aec6-3c55-a6d218bfb107'class ='data -table row-border display'cellspacing ='
0'width

Answers:


817

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

对于较旧的浏览器和node / jsdom:(<template>在撰写本文时尚不支持元素),请使用以下方法。库用于从HTML字符串中获取DOM元素的操作是相同的(为IE实现,还需要一些额外的工作来解决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字符串创建元素的方法:


1
如何在不使用这种不安全的innerHTML分配的情况下使用jQuery将innerHTML设置为创建的div(div.innerHTML =“某个值”)
Shivanshu Goyal

11
函数名称createElementFromHTML 具有误导性,因为div.firstChild返回的a Node不是HTMLElementeg can node.setAttribute。从函数创建Element返回div.firstElementChild
Semmel

谢谢,<div>包装我添加的HTML .innerHTML令人讨厌。我从来没有想过使用.firstChild
伊万

我正在尝试在创建的SVG中解析SVG div,并且输出是[object SVGSVGElement]在控制台日志为我提供正确的DOM元素的同时。我究竟做错了什么?
ed1nh0

1
我将使用firstElementChild而不是firstChild(请参阅w3schools.com/jsref/prop_element_firstelementchild.asp),因为如果模板的前面或结尾有空间,则firstChild将返回空的textNode
Chris Panayotoff,2016年

342

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

<template>元件用于声明可以在脚本中使用的HTML的片段。元素在DOM中表示为HTMLTemplateElement,其.content属性为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>');

请注意,使用不同容器元素(例如容器)的div类似方法不太可行。HTML对允许哪些元素类型存在于其他元素类型中有限制;例如,您不能将a td作为的直接子对象div。这将导致这些元素,如果你尝试设置消失innerHTMLdiv遏制他们。以来<template> s对它们的内容没有任何限制,因此使用模板时此缺点不适用。

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

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


9
不错的一种,现代的方法。在Opera上经过测试,让我们忘记IE
Adi Prasetyo

2
这是一种有效的方法,非常干净;但是,(至少在Chrome 50中),这会中断脚本标签的处理。换句话说,使用此方法创建脚本标签,然后将其附加到文档(正文或头部)不会导致对标签进行评估,因此会阻止脚本执行。(如果评估是在附加条件下进行的,这可能是设计
使然

1
可爱!您甚至可以通过执行类似的操作来查询元素:template.content.querySelector(“ img”);
Roger Gajraj

1
@Crescent Fresh:公平地说,我的印象是您希望您的答案消失,因为您说现在已经无关紧要了,我很抱歉。一个很好的折衷办法是在您的答案中添加一条语句,以使读者指出这一点,就像现在在meta上所讨论的那样 -我们只希望编辑之战不会从那儿恢复过来。
BoltClock

2
有一个问题。如果您在其中有一个标签,在标签的开始或结尾处带有空格(!),则它们将被删除!不要误会我的意思,这不是关于html.trim内部HTML设置的内部解析而是通过内部解析来删除的空格。就我而言,它删除了textNode中重要的空间。:-(
e-motiv

109

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

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


9
首先,insertAdjacentHTML适用于IE 4.0以后的所有浏览器。
JonasÄppelgran'18

6
第二,太好了!与之相比,最大的优点innerHTML += ...是使用此方法对以前元素的引用仍然完整。
JonasÄppelgran'18

7
对于第一个参数三,可能的值是:beforebeginafterbeginbeforeendafterend。请参阅MDN文章。
乔纳斯·阿佩格兰(JonasÄppelgran)

给我最好的答案
约旦

4
太糟糕了,它没有返回插入的HTML作为元素
Mojimi

30

较新的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

3
注意,这与设置innerHTMLdiv的缺点类似。某些元素(如td)将被忽略,并且不会出现在结果片段中。
Mark Amery

“有报道说桌面Safari曾经支持Range.createContextualFragment(),但至少在Safari 9.0和9.1中不支持。” (答案中的MDN链接)
akauppi '16

27

无需任何调整,您就获得了本机API:

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

9
与接受的答案一样,它也有相同的主要缺点-它将破坏HTML之类的<td>text</td>。这是因为DOMParser试图解析完整的HTML文档,并且并非所有元素都有效作为文档的根元素。
Mark Amery

4
-1,因为这是先前答案的重复,后者明确指出了我在上面的评论中提到的缺点。
Mark Amery

20

对于某些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.cloneNode(true);
}

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

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);

4
@MarkAmery的区别在于,他使用一个片段来允许将多个根元素附加到DOM中,这是一个额外的好处。如果威廉只提那是他的答案……
科恩。

10

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


5
即使这创建了一个DocumentFragment对象,但令我惊讶的是,它仍然遭受与公认答案相同的缺陷:如果这样做document.createRange().createContextualFragment('<td>bla</td>'),您将得到一个仅包含文本'bla'而不包含<td>元素的片段。至少,这就是我在Chrome 63中观察到的;我没有深入研究规范来确定它是否正确
Mark Amery


8

我正在使用此方法(在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>

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 litterals
var a = 10, b = 5
var foo=`
<img 
  onclick="alert('The future start today!')"   
  src='//placekitten.com/100/100'>
foo${a + b}
  <i>bar</i>
    <hr>`.toDOM();
document.body.appendChild(foo);
img {cursor: crosshair}

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

https://caniuse.com/template-literals


4

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

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

请注意,正如此页其他地方所指出的那样,这不适用于td
Mark Amery

3

HTML5和ES6

<template>

演示版

"use strict";

/**
 *
 * @author xgqfrms
 * @license MIT
 * @copyright xgqfrms
 * @description HTML5 Template
 * @augments
 * @example
 *
 */

/*

<template>
    <h2>Flower</h2>
    <img src="https://www.w3schools.com/tags/img_white_flower.jpg">
</template>


<template>
    <div class="myClass">I like: </div>
</template>

*/

const showContent = () => {
    // let temp = document.getElementsByTagName("template")[0],
    let temp = document.querySelector(`[data-tempalte="tempalte-img"]`),
        clone = temp.content.cloneNode(true);
    document.body.appendChild(clone);
};

const templateGenerator = (datas = [], debug = false) => {
    let result = ``;
    // let temp = document.getElementsByTagName("template")[1],
    let temp = document.querySelector(`[data-tempalte="tempalte-links"]`),
        item = temp.content.querySelector("div");
    for (let i = 0; i < datas.length; i++) {
        let a = document.importNode(item, true);
        a.textContent += datas[i];
        document.body.appendChild(a);
    }
    return result;
};

const arr = ["Audi", "BMW", "Ford", "Honda", "Jaguar", "Nissan"];

if (document.createElement("template").content) {
    console.log("YES! The browser supports the template element");
    templateGenerator(arr);
    setTimeout(() => {
        showContent();
    }, 0);
} else {
    console.error("No! The browser does not support the template element");
}
@charset "UTf-8";

/* test.css */

:root {
    --cololr: #000;
    --default-cololr: #fff;
    --new-cololr: #0f0;
}

[data-class="links"] {
    color: white;
    background-color: DodgerBlue;
    padding: 20px;
    text-align: center;
    margin: 10px;
}
<!DOCTYPE html>
<html lang="zh-Hans">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Template Test</title>
    <!--[if lt IE 9]>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
    <![endif]-->
</head>

<body>
    <section>
        <h1>Template Test</h1>
    </section>
    <template data-tempalte="tempalte-img">
        <h3>Flower Image</h3>
        <img src="https://www.w3schools.com/tags/img_white_flower.jpg">
    </template>
    <template data-tempalte="tempalte-links">
        <h3>links</h3>
        <div data-class="links">I like: </div>
    </template>
    <!-- js -->
</body>

</html>


2

迟了,但只是作为笔记;

可以向目标元素添加一个琐碎的元素作为容器,并在使用后将其删除。

//在chrome 23.0,firefox 18.0(即7-8-9和Opera 12.11)上进行了测试。

<div id="div"></div>

<script>
window.onload = function() {
    var foo, targetElement = document.getElementById('div')
    foo = document.createElement('foo')
    foo.innerHTML = '<a href="#" target="_self">Text of A 1.</a> '+
                    '<a href="#" onclick="return !!alert(this.innerHTML)">Text of <b>A 2</b>.</a> '+
                    '<hr size="1" />'
    // Append 'foo' element to target element
    targetElement.appendChild(foo)

    // Add event
    foo.firstChild.onclick = function() { return !!alert(this.target) }

    while (foo.firstChild) {
        // Also removes child nodes from 'foo'
        targetElement.insertBefore(foo.firstChild, foo)
    }
    // Remove 'foo' element from target element
    targetElement.removeChild(foo)
}
</script>

2

从字符串呈现DOM的最快解决方案:

let render = (relEl, tpl, parse = true) => {
  if (!relEl) return;
  const range = document.createRange();
  range.selectNode(relEl);
  const child = range.createContextualFragment(tpl);
  return parse ? relEl.appendChild(child) : {relEl, el};
};

这里 ü可以检查DOM操作的性能阵营VS本土JS

现在您可以简单地使用:

let element = render(document.body, `
<div style="font-size:120%;line-height:140%">
  <p class="bold">New DOM</p>
</div>
`);

当然,在不久的将来,您会使用内存中的引用,因为var“ element”是您在文档中新创建的DOM。

并记住“ innerHTML =“非常慢:/


1

这是我的代码,它可以正常工作:

function parseTableHtml(s) { // s is string
    var div = document.createElement('table');
    div.innerHTML = s;

    var tr = div.getElementsByTagName('tr');
    // ...
}

1
如果要创建的元素本身是表,则失败。
Mark Amery

0

对于这个问题,我想我会分享我提出的复杂但简单的方法……也许有人会发现有用的东西。

/*Creates a new element - By Jamin Szczesny*/
function _new(args){
    ele = document.createElement(args.node);
    delete args.node;
    for(x in args){ 
        if(typeof ele[x]==='string'){
            ele[x] = args[x];
        }else{
            ele.setAttribute(x, args[x]);
        }
    }
    return ele;
}

/*You would 'simply' use it like this*/

$('body')[0].appendChild(_new({
    node:'div',
    id:'my-div',
    style:'position:absolute; left:100px; top:100px;'+
          'width:100px; height:100px; border:2px solid red;'+
          'cursor:pointer; background-color:HoneyDew',
    innerHTML:'My newly created div element!',
    value:'for example only',
    onclick:"alert('yay')"
}));

0

为什么不使用本机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=range.createContextualFragment(s);
    e.appendChild(f);
    e = e.firstElementChild;

-1
function domify (str) {
  var el = document.createElement('div');
  el.innerHTML = str;

  var frag = document.createDocumentFragment();
  return frag.appendChild(el.removeChild(el.firstChild));
}

var str = "<div class='foo'>foo</div>";
domify(str);

-2

您可以使用以下函数将文本“ HTML”转换为元素

function htmlToElement(html)
{
  var element = document.createElement('div');
  element.innerHTML = html;
  return(element);
}
var html="<li>text and html</li>";
var e=htmlToElement(html);


-1; 这与接受的答案中提出的技术相同,并且具有相同的缺点-特别是不适用于td
Mark Amery

-3
var jtag = $j.li({ child:'text' }); // Represents: <li>text</li>
var htmlContent = $('mylist').html();
$('mylist').html(htmlContent + jtag.html());

使用jnerator



-3

var msg =“ test” jQuery.parseHTML(msg)


感谢您提供此代码段,它可能会提供一些有限的即时帮助。通过说明为什么这是一个很好的解决方案的方法,正确的解释将大大提高其长期价值,并使之对于其他存在类似问题的读者来说更有用。请编辑您的答案以添加一些解释,包括您所做的假设。
Dwhitz

-7

这也将起作用:

$('<li>').text('hello').appendTo('#mylist');

感觉更像是带有链接函数调用的jquery方式。


26
因为是jQuery,它感觉像jQuery吗?
蒂姆·费雷尔

5
最后一行真是太好笑了!
kumarharsh 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.