Створення нового елемента DOM з рядка HTML за допомогою вбудованих методів DOM або прототипу


621

У мене є HTML - рядок , що представляє елемент: '<li>text</li>'. Я хотів би додати його до елемента в DOM (a ulв моєму випадку). Як я можу це зробити за допомогою прототипу або методів DOM?

(Я знаю, що я міг би це легко зробити в jQuery, але, на жаль, ми не використовуємо jQuery.)


Вибачте, що ви намагаєтеся зробити саме? Рядок HTML -> елемент dom?
— Півмісяць свіжий

36
Прикро, що ці рішення є настільки непрямими. Я хотів би, щоб комітет з питань стандартів вказав щось подібне на кшталт:var nodes = document.fromString("<b>Hello</b> <br>");
— Шрідхар Сарнобат,


1
У мене виникли проблеми з вищезазначеним, тому що у мене були атрибути, які потрібно було передавати разом, і мені не здавалося, що їх аналізують: "<table id = '5ccf9305-4b10-aec6-3c55-a6d218bfb107' class = 'таблиця даних відображення меж рядка 'cellpacing =' 0 'width =' 100% '> </table> ", тому я просто використав: $ (" <table id =' 5ccf9305-4b10-aec6-3c55-a6d218bfb107 'class =' ​​data) -дисплей з межею рядків 'cellpacing =' 0 'width =' 100% '> </table> ")
— stevenlacerda

Відповіді:


817

Примітка: більшість сучасних браузерів підтримують <template>елементи HTML , які забезпечують більш надійний спосіб перетворити створення елементів із рядків. Детальніше дивіться у відповіді Марка Амери .

Для старих браузерів і node / jsdom : (який ще не підтримує <template>елементи на момент написання), використовуйте наступний метод. Це те саме, що і бібліотеки роблять, щоб отримати елементи DOM із HTML-рядку ( з деякою додатковою роботою для 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 це не працюватиме для деяких елементів, які юридично не можуть бути дітьми а <div>, наприклад <td>s.

Якщо ви вже використовуєте бібліотеку, я рекомендую вам дотримуватися затвердженого бібліотекою способу створення елементів з рядків HTML:


1
Як встановити innerHTML до створеного div за допомогою jQuery, не використовуючи це небезпечне призначенняHTML (div.innerHTML = "деяке значення")
— Shivanshu Goyal

11
Назва функції createElementFromHTML вводить в оману, оскільки div.firstChildповертає a, Nodeяке не є, HTMLElementнаприклад, не може node.setAttribute. Щоб Elementнатомість створити повернення div.firstElementChildвід функції.
— Semmel

Дякую, <div>обгортання HTML, який я додав, .innerHTMLдратувало мене. Я ніколи не думав використовувати .firstChild.
— Іван

Я намагаюся проаналізувати SVG всередині створеного, divі вихід є, [object SVGSVGElement]поки журнал консолі дає мені правильний елемент DOM. Що я роблю неправильно?
— ed1nh0

1
Я б використав firstElementChild замість firstChild (див. W3schools.com/jsref/prop_element_firstelementchild.asp ), тому що якщо перед або в кінці шаблону є пробіл, firstChild поверне порожній текстNode
— Chris Panayotoff

342

HTML 5 представив <template>елемент, який можна використовувати для цієї мети (як описано зараз у специфікаціях WhatWG та MDN) ).

<template>Елемент використовується для оголошення фрагменти HTML , які можуть бути використані в сценаріях. Елемент представлений у DOM як a, HTMLTemplateElementякий має .contentвластивість DocumentFragmentтипу, забезпечувати доступ до вмісту шаблону. Це означає , що ви можете перетворити рядок HTML для елементів DOM, встановивши innerHTMLз <template>елемента, потім досягаючи в template«S .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 має обмеження щодо того, які типи елементів можуть існувати, в яких інших типах елементів; наприклад, ви не можете поставити а tdяк пряму дитину div. Це призводить до зникнення цих елементів, якщо ви спробуєте встановити innerHTMLа, divщоб він містив їх. З тих пір<template> s не мають таких обмежень щодо їх вмісту, цей недолік не застосовується при використанні шаблону.

Однак templateвін не підтримується в деяких старих браузерах. Чи можу я використати станом на січень 2018 року ... приблизно 90% користувачів у всьому світі користуються браузером, який підтримує templates . Зокрема, жодна версія Internet Explorer не підтримує їх; Microsoft не реалізувала templateпідтримку до випуску Edge.

Якщо вам пощастило писати код, орієнтований лише на користувачів у сучасних браузерах, продовжуйте користуватися ними прямо зараз. В іншому випадку, можливо, доведеться почекати деякий час, коли користувачі зможуть наздогнати.


9
приємний, сучасний підхід. Тестований на Opera, забудьмо IE
— Adi Prasetyo

2
Це ефективний підхід і дуже чистий; однак, принаймні, у Chrome 50, це порушує обробку тегів сценарію. Іншими словами, використання цього методу для створення тегу сценарію, а потім додавання його до документа (тіла або голови) не призводить до того, що тег оцінюється, а значить, не дозволяє виконувати сценарій. (Це може бути
— замислене,

1
ЛЮБИТЬ! Ви навіть можете запитувати елементи, виконавши щось на зразок: template.content.querySelector ("img");
— Роджер Гаджрай

1
@Crescent Fresh: Досить справедливо, у мене склалося враження, що ти хочеш, щоб твоя відповідь пішла, оскільки ти сказав, що це не має значення, мої вибачення. Хорошим компромісом було б додати до вашої відповіді заяву, яка вказує читачів на цю, як зараз обговорювалося на мета - сподіваємось, що війна редагування не зникне звідти.
— BoltClock

2
У цьому є одна проблема. Якщо у вас є тег із пробілами всередині (!) На початку чи в кінці, вони будуть видалені! Не зрозумійте мене неправильно, це не про пробіли, видаленіhtml.trim допомогою внутрішнього розбору внутрішніх налаштувань HTMLHT. У моєму випадку він видаляє важливі пробіли, що є частиною 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

6
По-друге, це чудово! Великий плюс порівняно з innerHTML += ...тим, що за допомогою цього методу досі недоторканні посилання на попередні елементи.
— Jonas Äppelgran

7
В- третіх, можливі значення першого аргументу: beforebegin, afterbegin, beforeend, afterend. Дивіться статтю MDN.
— Jonas Äppelgran

найкраща відповідь мені
— Йордан

4
Шкода, що він не повертає вставлений HTML як елемент
— Моїмі

30

Новіші реалізації DOM мають те range.createContextualFragment, що робить те, що ви хочете, незалежно від рамки.

Це широко підтримується. Щоб бути впевненим, перевірте його сумісність в тому ж MDN-посиланні, оскільки воно буде змінюватися. Станом на травень 2017 року це:

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

3
Зауважте, що це має подібні недоліки, ніж налаштування innerHTMLключа; певні елементи, як-от tds, будуть ігноровані та не відображатимуться в отриманому фрагменті.
— Марк Амері

"Є повідомлення, що Safari на робочому столі в один момент підтримував Range.createContextualFragment (), але він не підтримується принаймні в Safari 9.0 і 9.1." (Посилання MDN у відповіді)
— akauppi

27

Не потрібно жодних налаштувань, у вас є власний API:

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

9
Це страждає тим самим головним недоліком, що і прийнята відповідь - він маніпулює HTML, як <td>text</td>. Це відбувається тому DOMParser, що намагається розібрати повний HTML-документ , і не всі елементи дійсні як кореневі елементи документа.
— Марк Амері

4
-1 тому, що це дублікат попередньої відповіді, який прямо вказував на недолік, згаданий у моєму коментарі вище.
— Марк Амері

20

Для певних фрагментів HTML, таких як <td>test</td>div.innerHTML, DOMParser.parseFromString та range.createContextualFragment (без правильного контексту) рішення, згадані в інших відповідях тут, не створюють <td>елемент.

jQuery.parseHTML () обробляє їх належним чином (я вилучив функцію parseHTML jQuery 2 в незалежну функцію яку можна використовувати в без 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; Я не вникав в специфікації , щоб з'ясувати , чи є це правильне поводження чи ні.
— Марк Амері

9

З прототипом ви також можете:

HTML:

<ul id="mylist"></ul>

JS:

$('mylist').insert('<li>text</li>');

Це jQuery? чи JS?
— Jeya Suriya Muthumari

1
Для цього використовуються не вбудовані методи DOM jQuery, як згадується.
— Хуан Хуртадо

1
@JuanHurtado для цього використовуються прототипи, як просили ОП майже 11 років тому;)
— Пабло Борович

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 (), який ми можемо знайти в різних місцях, тепер ми можемо безпечно використовувати зворотні посилання ( буквальні шаблони) ).

Таким чином, ми можемо мати одинарні та подвійні лапки у фу html-декларації.

Це поводяться як гередоки для тих, хто знайомий з цим терміном.

Це також можна покращити за допомогою змінних, щоб скласти складні шаблони:

Літерали шаблону додаються символом зворотного галочки ( ) (серйозний наголос) замість подвійних або одиничних лапок. Літерали шаблону можуть містити заповнювачі. Вони позначаються знаком долара та фігурними дужками ($ {вираження}). Вирази в заповнювачах і текст між ними передаються функції. Функція за замовчуванням просто об'єднує частини в одну рядок. Якщо є вираз, що передує шаблону літералу (тут позначено тег), це називається "шаблон із тегом". У цьому випадку вираз тегу (зазвичай це функція) викликається з оброблюваним літералом шаблону, яким ви можете потім маніпулювати перед виведенням. Щоб уникнути зворотного галочки в буквальному шаблоні, поставте зворотний проріз \ перед зворотним галочкою.

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

Зауважте, що - як зазначено в іншому місці на цій сторінці - це не працює для tds.
— Марк Амері

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 React vs нативної JS

Тепер ви можете просто використовувати:

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

І звичайно, в найближчому майбутньому ви використовуєте посилання з пам'яті, тому що "елемент" - ваш новий створений DOM у вашому документі.

І пам'ятайте, що "innerHTML =" дуже повільно: /


1

Ось мій код, і він працює:

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

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

1
Виходить з ладу, якщо елемент, який слід створити, - це сама таблиця.
— Марк Амері

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; це та сама техніка, що запропонована у прийнятій відповіді, і має ті самі недоліки - зокрема, не працює для tds.
— Марк Амері

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

Використовуйте jnerator


-3

Ось для мене працює робочий код

Я хотів перетворити рядок " Текст " у елемент HTML

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

Що таке wwww?
— Tintin81

-3

var msg = "тест" jQuery.parseHTML (msg)


Дякуємо за цей фрагмент коду, який може надати деяку обмежену, негайну допомогу. Належне пояснення буде значно поліпшити свою довгострокову цінність, показуючи , чому це є хорошим рішенням проблеми і зробить його більш корисним для читачів майбутніх з іншими подібними питаннями. Будь ласка , змініть свій відповідь , щоб додати деякі пояснення, в тому числі припущень , які ви зробили.
— Dwhitz

-7

Це також буде працювати:

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

Це більше схоже на спосіб jquery з викликом прикованої функції.


26
Це відчувається як jQuery, тому що це jQuery?
— Тім Феррелл

5
Цей останній рядок просто веселий!
— кумархарш
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.