Створення нового елемента 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.