Creazione di un nuovo elemento DOM da una stringa HTML mediante metodi DOM o prototipo integrati


621

Ho una stringa HTML che rappresenta un elemento: '<li>text</li>'. Vorrei aggiungerlo a un elemento nel DOM (a ulnel mio caso). Come posso farlo con Prototype o con i metodi DOM?

(So ​​che potrei farlo facilmente in jQuery, ma sfortunatamente non stiamo usando jQuery.)


Scusa, cosa stai cercando di realizzare esattamente? Stringa HTML -> elemento dom?
Crescent Fresh,

36
È un peccato che queste soluzioni siano così indirette. Vorrei che il comitato per le norme specificasse qualcosa di simile come:var nodes = document.fromString("<b>Hello</b> <br>");
Sridhar Sarnobat,


1
Ho avuto un problema con quanto sopra, perché avevo attributi che dovevano essere passati e non avevo voglia di analizzarli: "<table id = '5ccf9305-4b10-aec6-3c55-a6d218bfb107' class = 'data-table riga-border display 'cellspacing =' 0 'width =' 100% '> </table> "così, ho semplicemente usato: $ (" <table id =' 5ccf9305-4b10-aec6-3c55-a6d218bfb107 'class =' ​​data -table display-border line 'cellspacing =' 0 'width =' 100% '> </table> ")
stevenlacerda

Risposte:


818

Nota: la maggior parte dei browser attuali supporta <template>elementi HTML , che forniscono un modo più affidabile di trasformare la creazione di elementi dalle stringhe. Vedi la risposta di Mark Amery qui sotto per i dettagli .

Per i browser più vecchi e node / jsdom : (che non supporta ancora gli <template>elementi al momento della scrittura), utilizzare il seguente metodo. È la stessa cosa che le librerie usano per ottenere elementi DOM da una stringa HTML ( con qualche lavoro extra per IE per aggirare i bug con la sua implementazione di 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; 
}

Si noti che, diversamente dai modelli HTML, questo non funzionerà per alcuni elementi che non possono essere legalmente figli di a <div>, come ad esempio <td>s.

Se stai già utilizzando una libreria, ti consiglio di attenerti al metodo approvato dalla libreria per creare elementi da stringhe HTML:


1
Come impostare innerHTML sul div creato usando jQuery senza usare questa assegnazione innerHTML non sicura (div.innerHTML = "un certo valore")
Shivanshu Goyal

12
Il nome della funzione createElementFromHTML è fuorviante poiché div.firstChildrestituisce un Nodeche non è un HTMLElementesempio impossibile node.setAttribute. Per creare invece un Elementritorno div.firstElementChilddalla funzione.
Semmel,

Grazie, il <div>wrapping dell'HTML che ho aggiunto .innerHTMLmi ha infastidito. Non ho mai pensato di usare .firstChild.
Ivan,

Sto cercando di analizzare un SVG all'interno del creato dive l'output è [object SVGSVGElement]mentre il registro della console mi dà l'elemento DOM corretto. Che cosa sto facendo di sbagliato?
ed1nh0,

1
Vorrei usare firstElementChild invece di firstChild (vedi w3schools.com/jsref/prop_element_firstelementchild.asp ), perché se c'è spazio davanti o alla fine del modello, il primo bambino restituirebbe textNode vuoto
Chris Panayotoff

344

HTML 5 ha introdotto l' <template>elemento che può essere utilizzato per questo scopo (come ora descritto nelle specifiche WhatWG e nei documenti MDN ).

Un <template>elemento viene utilizzato per dichiarare frammenti di HTML che possono essere utilizzati negli script. L'elemento è rappresentato nel DOM come un HTMLTemplateElementoggetto con una .contentproprietà di DocumentFragmenttipo, per fornire accesso al contenuto del modello. Ciò significa che è possibile convertire una stringa HTML da elementi DOM impostando la innerHTMLdi un <template>elemento, poi raggiungendo nel template's .contentproprietà.

Esempi:

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

Si noti che approcci simili che utilizzano un diverso elemento contenitore come undiv non funzionano del tutto. L'HTML ha restrizioni su quali tipi di elementi possono esistere all'interno dei quali altri tipi di elementi; per esempio, non puoi mettere a tdcome figlio diretto di a div. Questo fa sì che questi elementi svaniscano se provi a impostare innerHTMLdi a divper contenerli. Poiché gli <template>s non hanno tali restrizioni sul loro contenuto, questa carenza non si applica quando si utilizza un modello.

Tuttavia, templatenon è supportato in alcuni vecchi browser. A gennaio 2018, posso usare ... si stima che il 90% degli utenti a livello globale stia utilizzando un browser che supporti templates . In particolare, nessuna versione di Internet Explorer li supporta; Microsoft non ha implementato il templatesupporto fino al rilascio di Edge.

Se sei abbastanza fortunato da scrivere codice indirizzato solo agli utenti sui browser moderni, vai avanti e usali subito. Altrimenti, potrebbe essere necessario attendere un po 'prima che gli utenti possano recuperare.


9
bello, approccio moderno. Testato su Opera, dimentichiamoci di IE
Adi Prasetyo,

2
Questo è un approccio efficace e molto pulito; tuttavia (almeno in Chrome 50) questo interrompe la gestione dei tag di script. In altre parole, l'uso di questo metodo per creare un tag di script e quindi aggiungerlo al documento (body o head) non comporta la valutazione del tag e quindi impedisce l'esecuzione dello script. (Questo può essere in base alla progettazione se la valutazione si verifica in allegato; non potrei dirlo con certezza.)
shanef22

1
BELLO! puoi persino cercare elementi facendo qualcosa del tipo: template.content.querySelector ("img");
Roger Gajraj,

1
@Crescent Fresh: Abbastanza giusto, ho avuto l'impressione che volevi che la tua risposta sparisse da quando hai detto che ora è irrilevante, le mie scuse. Un buon compromesso sarebbe l'aggiunta di una dichiarazione alla tua risposta che indichi i lettori a questo, come è stato ora discusso sul meta - speriamo solo che la guerra di modifica non riprenda da lì.
BoltClock

2
C'è un problema con esso. Se hai un tag lì dentro con spazi (!) All'inizio o alla fine, verranno rimossi! Non fraintendetemi, non si tratta degli spazi rimossi da html.trimma dall'analisi interna dell'impostazione innerHTML. Nel mio caso rimuove gli spazi importanti facenti parte di un textNode. :-(
e-motiv

110

Utilizzare insertAdjacentHTML () . Funziona con tutti i browser attuali, anche con IE11.

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


9
Innanzitutto, insertAdjacentHTMLfunziona con tutti i browser a partire da Internet Explorer 4.0.
Jonas Äppelgran,

6
Secondo, è fantastico! Un grande vantaggio rispetto a innerHTML += ...è che i riferimenti agli elementi precedenti sono ancora intatti usando questo metodo.
Jonas Äppelgran,

7
In terzo luogo, i valori possibili per il primo argomento è: beforebegin, afterbegin, beforeend, afterend. Vedi l'articolo MDN.
Jonas Äppelgran,

migliore risposta a me
Giordania

4
Peccato che non restituisca l'HTML inserito come elemento
Mojimi

30

Le implementazioni DOM più recenti hanno range.createContextualFragment, che fa quello che vuoi in modo indipendente dal framework.

È ampiamente supportato. Per sicurezza, controlla la sua compatibilità nello stesso link MDN, poiché cambierà. A partire da maggio 2017 è questo:

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

3
Si noti che ciò presenta svantaggi simili all'impostazione innerHTMLdi un div; alcuni elementi, come tds, verranno ignorati e non compariranno nel frammento risultante.
Mark Amery,

"Ci sono rapporti secondo cui Safari desktop in un certo momento supporta Range.createContextualFragment (), ma non è supportato almeno in Safari 9.0 e 9.1." (Link MDN nella risposta)
akauppi

27

Non c'è bisogno di alcuna modifica, hai un'API nativa:

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

9
Ciò presenta lo stesso grande svantaggio della risposta accettata: manterrà HTML come <td>text</td>. Questo perché DOMParsersta cercando di analizzare un documento HTML completo e non tutti gli elementi sono validi come elementi radice di un documento.
Mark Amery,

4
-1 perché questo è un duplicato di una risposta precedente che ha esplicitamente sottolineato lo svantaggio menzionato nel mio commento sopra.
Mark Amery, il

20

Per alcuni frammenti html come <td>test</td>, div.innerHTML, DOMParser.parseFromString e range.createContextualFragment (senza il giusto contesto) le soluzioni citate in altre risposte qui, non creeranno l' <td>elemento.

jQuery.parseHTML () li gestisce correttamente (ho estratto la funzione parseHTML di jQuery 2 in una funzione indipendente che può essere utilizzata in codebase non jquery).

Se si supporta solo Edge 13+, è più semplice utilizzare solo il tag modello HTML5:

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

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

16

Ecco un modo semplice per farlo:

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 la differenza qui è che usa un frammento per consentire l'aggiunta di più elementi root nel DOM, che è un ulteriore vantaggio. Se solo William avesse menzionato questa è la sua risposta ...
Koen.

10

È possibile creare nodi DOM validi da una stringa utilizzando:

document.createRange().createContextualFragment()

L'esempio seguente aggiunge un elemento pulsante nella pagina prendendo il markup da una stringa:

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
Anche se questo crea un DocumentFragmentoggetto, tuttavia - con mia grande sorpresa - soffre dello stesso difetto della risposta accettata: se lo fai document.createRange().createContextualFragment('<td>bla</td>'), ottieni un frammento che contiene solo il testo "bla" senza l' <td>elemento. O almeno, è quello che osservo in Chrome 63; Non ho approfondito le specifiche per capire se si tratta del comportamento corretto o meno.
Mark Amery, il


8

Sto usando questo metodo (funziona in IE9 +), anche se non analizzerà <td>o altri figli diretti diretti non validi del corpo:

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

Per migliorare ulteriormente l'utile frammento .toDOM () che possiamo trovare in luoghi diversi, ora possiamo tranquillamente usare i backtick ( letterali template ).

Quindi possiamo avere virgolette singole e doppie nella dichiarazione foo html.

Questo si comporta come heredocs per coloro che hanno familiarità con il termine.

Questo può essere ulteriormente migliorato con le variabili, per creare modelli complessi:

I letterali del modello sono racchiusi dal carattere back-tick ( ) (accento grave) anziché dalle virgolette doppie o singole. I letterali modello possono contenere segnaposto. Questi sono indicati dal simbolo del dollaro e dalle parentesi graffe ($ {espressione}). Le espressioni tra i segnaposto e il testo tra loro vengono passati a una funzione. La funzione predefinita concatena le parti in un'unica stringa. Se esiste un'espressione che precede il modello letterale (tag qui), questo si chiama "modello con tag". In tal caso, l'espressione tag (di solito una funzione) viene chiamata con il modello letterale elaborato, che è quindi possibile manipolare prima dell'output. Per evitare un segno di spunta in un modello letterale, inserire una barra rovesciata \ prima del segno di spunta.

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}

Quindi, perché non usare direttamente .innerHTML +=? In questo modo, l'intero DOM viene ricalcolato dal browser, è molto più lento.

https://caniuse.com/template-literals


4

Ho aggiunto un Documentprototipo che crea un elemento dalla stringa:

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

Nota che - come indicato altrove in questa pagina - questo non funzionerà per tds.
Mark Amery, il

3

HTML5 ed ES6

<template>

dimostrazione

"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

In ritardo, ma solo come una nota;

È possibile aggiungere un elemento banale all'elemento target come contenitore e rimuoverlo dopo l'uso.

// Testato su Chrome 23.0, Firefox 18.0, ovvero 7-8-9 e 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

La soluzione più veloce per rendere DOM dalla stringa:

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

E qui puoi controllare le prestazioni della manipolazione DOM React vs JS nativo

Ora puoi semplicemente usare:

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

E, naturalmente, in un prossimo futuro usi riferimenti dalla memoria perché var "element" è il tuo nuovo DOM creato nel tuo documento.

E ricorda "innerHTML =" è molto lento: /


1

Ecco il mio codice e funziona:

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

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

1
Non riesce se l'elemento da creare è esso stesso una tabella.
Mark Amery,

0

Per diamine, ho pensato di condividere questo approccio complicato ma semplice che mi è venuto in mente ... Forse qualcuno troverà qualcosa di utile.

/*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

Perché non fare con js nativo?

    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

È possibile utilizzare la seguente funzione per convertire il testo "HTML" nell'elemento

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; questa è la stessa tecnica proposta nella risposta accettata e presenta gli stessi inconvenienti - in particolare, non funziona per tds.
Mark Amery,

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

Usa jnerator


-3

Ecco il codice funzionante per me

Volevo convertire la stringa ' Text ' in elemento HTML

var diva = UWA.createElement('div');
diva.innerHTML = '<a href="http://wwww.example.com">Text</a>';
var aelement = diva.firstChild;

Che cosa è wwww?
Tintin81,

-3

var msg = "test" jQuery.parseHTML (msg)


Grazie per questo frammento di codice, che potrebbe fornire un aiuto immediato e limitato. Una spiegazione adeguata migliorerebbe notevolmente il suo valore a lungo termine mostrando perché questa è una buona soluzione al problema e la renderebbe più utile ai futuri lettori con altre domande simili. Si prega di modificare la risposta di aggiungere qualche spiegazione, tra le ipotesi che hai fatto.
Dwhitz,

-7

Funzionerà anche questo:

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

Sembra più un modo jquery con le chiamate di funzione incatenate.


26
Ti sembra jQuery perché è jQuery?
Tim Ferrell,

5
L'ultima riga è semplicemente divertente!
Kumarharsh,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.